Skip to content

Content API

Gemmail message format: addresses, timestamps, and gemtext body.

GemmailMessage

GemmailMessage dataclass

GemmailMessage(
    senders: list[MisfinAddress] = list(),
    recipients: list[MisfinAddress] = list(),
    timestamps: list[datetime] = list(),
    body: str = "",
)

from_bytes_b classmethod

from_bytes_b(data: bytes) -> GemmailMessage

Parse a Misfin(B) gemtext message body.

B-format embeds metadata as special-prefix lines anywhere in the body: < sender, : recipients, @ timestamp. All < lines are collected as senders (forwarding chains); only the first : and @ are used. Parsed metadata lines are stripped from the returned body.

Source code in src/titlani/content/gemmail.py
@classmethod
def from_bytes_b(cls, data: bytes) -> "GemmailMessage":
    """Parse a Misfin(B) gemtext message body.

    B-format embeds metadata as special-prefix lines anywhere in
    the body: ``< sender``, ``: recipients``, ``@ timestamp``.
    All ``< `` lines are collected as senders (forwarding chains);
    only the first ``: `` and ``@ `` are used. Parsed metadata
    lines are stripped from the returned body.
    """
    try:
        text = data.decode("utf-8")
    except UnicodeDecodeError as e:
        raise ValueError(f"Message is not valid UTF-8: {e}") from e

    for i, ch in enumerate(text):
        if ch == "\r" and (i + 1 >= len(text) or text[i + 1] != "\n"):
            raise ValueError("CR must only appear immediately before LF")

    text = text.replace("\r\n", "\n")
    lines = text.split("\n")

    senders, recipients, timestamps, body = _parse_b_metadata(lines)
    return cls(
        senders=senders,
        recipients=recipients,
        timestamps=timestamps,
        body=body,
    )

MisfinAddress

MisfinAddress dataclass

MisfinAddress(mailbox: str, hostname: str, blurb: str = '')