Build RFC 5322 / MIME messages as Bytes, safe for LLM-supplied input.
- No header injection. CR, LF and other control characters in
the subject and extra header values are folded to a single space;
addresses containing them, malformed addresses and malformed header
names are rejected with an Err. Structural headers (
Content-Type,MIME-Version, ...) cannot be overridden throughheaders. - Non-ASCII subjects, header values and display names are
RFC 2047 encoded (
=?UTF-8?B?...?=, folded at 75 characters); non-ASCII attachment names use RFC 2231filename*. - Bodies. Plain text, HTML, or both (
multipart/alternative), plus attachments (multipart/mixed). ASCII text with short lines is sent7bit; anything else is base64 with 76-character lines. Attachments are base64 (a forwardedmessage/rfc822is8bit). Everything is concatenated as Bytes, so binary attachments arrive intact.
Message fields (all optional except a body for sending): from,
to, cc, bcc, reply-to (a Str such as
"Ada <ada@example.com>, bob@example.com", a Vec of such Strs, or
{name, email} Maps), subject, text, html, attachments (a
Vec of {filename, content, mime-type?, content-id?, inline?},
content being Bytes or a Str), in-reply-to, references (a
Message-ID Str or Vec), and headers (extra headers as a Map).
Gmail adds Date and Message-ID and strips Bcc from delivered
copies.
::mime ::google::gmail::mime
raw ::mime/build-message({
to: ["Zoë Example <zoe@example.com>"],
subject: "Résumé attached",
text: "Hi Zoë, see attached.",
html: "<p>Hi Zoë, see attached.</p>",
attachments: [{filename: "résumé.pdf", content: Bytes([37, 80, 68, 70]), mime-type: "application/pdf"}]
})
Values
ATOM_PHRASE_PATTERN
ATOM_PHRASE_PATTERN: Str "^[A-Za-z0-9!#$%&'*+/=?^_`{|}~ -]+$"
CONTROL_PATTERN
CONTROL_PATTERN: Str "[\\x00-\\x08\\x0A-\\x1F\\x7F]+"
EMAIL_PATTERN
EMAIL_PATTERN: Str "^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$"
HEADER_NAME_PATTERN
HEADER_NAME_PATTERN: Str "^[!-9;-~]+$"
LINE_BREAK_PATTERN
LINE_BREAK_PATTERN: Str "[\\r\\n]"
MAX_PLAIN_HEADER
MAX_PLAIN_HEADER: Int 900
MIME_TYPE_PATTERN
MIME_TYPE_PATTERN: Str "^[A-Za-z0-9!#$&^_.+-]+/[A-Za-z0-9!#$&^_.+-]+$"
RESERVED_HEADERS
RESERVED_HEADERS: Vec [
"from",
"to",
"cc",
"bcc",
"reply-to",
"subject",
"in-reply-to",
"references",
"mime-version",
"content-type",
"content-transfer-encoding",
"content-disposition"
]
WORD_BYTES
WORD_BYTES: Int 39
Functions
address-header
fn (name: Str, value: Any): Any
address-items
fn (value: Any): Vec
attachment-error
fn (attachment: Any): Str?
Why an attachment Map is unusable (missing filename or content, a bad MIME type), or null (pure).
attachment-mime-type
fn (attachment: Map): Str
The MIME type for an attachment: its mime-type, else guessed from the filename, else application/octet-stream.
attachment-part
fn (attachment: Map): Bytes
An attachment part as Bytes: Content-Type with name, Content-Disposition (attachment, or inline with a content-id), and the content base64 encoded in 76-character lines. A message/rfc822 attachment (a forwarded email) is embedded as is, 8bit.
build-body
fn (message: Map, boundary: Str): Bytes
The body entity (its Content-Type headers and content) as Bytes:
text, html, or both as multipart/alternative; wrapped in
multipart/mixed with the attachments when there are any. Nested
boundaries derive from boundary (a_ / m_ prefixes).
build-headers
fn (message: Map): Str
The top-level header block of a message (pure), each line ending in
CRLF, ending with MIME-Version: 1.0: From, To, Cc, Bcc, Reply-To,
Subject, In-Reply-To, References, then message.headers. Returns an
Err for an invalid address or header name.
build-message
fn (message: Map): Bytes
fn (message: Map, boundary: Str): Bytes
Build a complete RFC 5322 message as Bytes from a message Map (see
the namespace doc for the fields). Returns an Err for invalid
addresses, header names or attachments. Pass boundary for
reproducible output in tests.
raw ::google::gmail::mime/build-message({to: "ada@example.com", subject: "Hi", text: "Hello!"})
build-related
fn (metadata: Map, message: Bytes, boundary: Str): Bytes
A Gmail media-upload body as Bytes (multipart/related): a JSON
metadata part (nulls dropped) and the raw message as
message/rfc822, concatenated as bytes. Send it with
Content-Type: multipart/related; boundary=<boundary>.
byte-length
fn (text: Str): Int
clean-header-value
fn (value: Any): Str
Fold every run of CR, LF and other control characters (tab excepted) to one space and trim, so a value can never start a new header line.
emails
fn (value: Any): Vec
Just the email addresses of an address field, lowercased; invalid entries are skipped (pure). Handy for comparing senders.
encode-header-text
fn (value: Any): Str
A header value safe to emit: control characters folded (clean-header-value), then RFC 2047 encoded when it is not ASCII or is very long.
encode-phrase
fn (name: Str): Str
A display name for an address header: as is when it is plain ASCII words, a quoted string when it has specials ("Doe, Jane"), or RFC 2047 encoded when it is not ASCII.
encode-words
fn (text: Str): Str
RFC 2047 encode text as UTF-8 B encoded words, split on character
boundaries by byte count so each word stays within 75 characters and
a character is never cut in two, joined with a folding CRLF SP: encode-words("Zoë") is "=?UTF-8?B?Wm/Dqw==?=".
filename-params
fn (filename: Str): Map
The name (Content-Type) and filename (Content-Disposition) parameters for a file name: quoted ASCII, or RFC 2047 / RFC 2231 encoded when not ASCII.
format-address
fn (address: Map): Str
Render {name, email} for a header: Ada <ada@example.com>, with the name quoted or RFC 2047 encoded as needed.
format-addresses
fn (addresses: Vec): Str
Render a Vec of {name, email} as one header value, comma separated (folded when long).
has-line-break
fn (s: Str): Bool
True when s contains a CR or LF.
header-error
fn (name: Any): Str?
is-ascii
fn (s: Str): Bool
True when s has only 7-bit ASCII characters.
is-email
fn (email: Any): Bool
True when email is a plain local@domain.tld address (ASCII; no display name, spaces or line breaks).
message-ids
fn (value: Any): Vec
multipart
fn (subtype: Str, parts: Vec, boundary: Str): Bytes
A multipart/<subtype> entity as Bytes: its Content-Type header, then each part (Bytes, already carrying its own headers) between boundary delimiters.
new-boundary
fn (): Str
A random MIME boundary. It contains =_, which never occurs in base64 content.
normalize-newlines
fn (text: Str): Str
Convert LF, CR and CRLF line endings to CRLF (what SMTP requires).
parse-addresses
fn (value: Any): Vec
Parse an address field (Str list, Vec, or {name, email} Map) into a Vec of {name, email}, or an Err naming the first invalid address.
read-address
fn (value: Any): Map
Parse one address ("ada@example.com", "Ada Lovelace <ada@example.com>", or {name, email}) into {name, email, error} (pure); error is null when the address is valid.
split-address-list
fn (value: Str): Vec
Split an address-list Str on commas outside double quotes: "\"Doe, Jane\" <j@x.io>, bob@x.io" gives two entries.
text-part
fn (text: Str, subtype: Str): Bytes
A text/<subtype>; charset="UTF-8" part (headers, blank line, body) as Bytes: 7bit when the text is ASCII with lines under 999 characters, else base64. Line endings become CRLF.
to-bytes
fn (content: Any): Bytes
Content as Bytes: Bytes pass through, a Str becomes its UTF-8 bytes, null is empty, anything else is JSON-encoded.
to-raw
fn (message: Any): Str
Base64url-encode a message (Bytes or Str) for the Gmail raw field.
unquote
fn (s: Str): Str
utf8-chunks
fn (text: Str, max-bytes: Int): Vec
wrap-base64
fn (encoded: Str): Str
Break a base64 Str into CRLF-separated lines of at most 76 characters (RFC 2045).