email

Parse and build RFC 5322 + MIME multipart email messages. The surface covers parsing inbound messages, inspecting headers and bodies, extracting attachments, decoding encoded-words and address lists, and building outgoing messages with text + HTML alternatives and attachments. Delivery-status (bounce) reports are lifted into structured form. Transport is out of scope — send/receive lives at the directive layer; this module is the data layer those transports produce or consume.

email:parse

[$email:parse] -> element — Parse RFC 5322 bytes into a [message] element, decoding headers, bodies, and the multipart tree.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: a@b.com\r\nTo: c@d.com\r\nSubject: Hi\r\n\r\nHello body\r\n"]]]
  [$email:is-multipart $msg]]
          
            false
          

email:emit

[$email:emit] -> bytes — Re-encode a [message] element to canonical RFC 5322 bytes; same input emits byte-identical output.

            [?lib 'cx-stdlib/email']
[?let [= $msg [$email:build {"from": "agent@example.com",
                            "to": "customer@example.com",
                            "subject": "Re: Invoice question",
                            "body": "Thank you for reaching out..."}]]
  [$email:subject [$email:parse [$email:emit $msg]]]]
          
            'Re: Invoice question'
          

email:headers

[$email:headers] -> map — Return all headers as a map of lowercased name to a [sequence string] of values.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: a@b.com\r\nSubject: Hi\r\n\r\nbody"]]]
  [$count [$email:headers $msg]]]
          
            2
          

email:header

[$email:header] -> string — Return the first value of the named header (case-insensitive), or empty string if absent.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: a@b.com\r\nSubject: Hi\r\n\r\nbody"]]]
  [$email:header $msg "subject"]]
          
            'Hi'
          

email:header-values

[$email:header-values] -> [sequence string] — Return every value of the named header (case-insensitive), for multi-instance headers like Received.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "Received: from a\r\nReceived: from b\r\nFrom: a@b.com\r\n\r\nbody"]]]
  [$email:header-values $msg "Received"]]
          
            ('from a', 'from b')
          

email:subject

[$email:subject] -> string — Return the Subject header with encoded-words decoded to UTF-8.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $crlf [$email:parse [$bytes:from-string-utf8 "Subject: Hi\r\n\r\nbody"]]]
  [= $lf [$email:parse [$bytes:from-string-utf8 "Subject: Hi\n\nbody"]]]
  [$eq [$email:subject $crlf] [$email:subject $lf]]]
          
            true
          

email:from-addr

[$email:from-addr] -> element — Return the first From address as an [address] element.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: John Doe \r\n\r\nbody"]]]
  [$email:format-address [$email:from-addr $msg]]]
          
            'John Doe '
          

email:to-addrs

[$email:to-addrs] -> [sequence element] — Return all To addresses, expanding any RFC 5322 groups.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "To: a@x.com, b@x.com\r\nFrom: c@y.com\r\n\r\nbody"]]]
  [$count [$email:to-addrs $msg]]]
          
            2
          

email:cc-addrs

[$email:cc-addrs] -> [sequence element] — Return all Cc addresses, expanding any RFC 5322 groups.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: c@y.com\r\nCc: a@x.com\r\n\r\nbody"]]]
  [$count [$email:cc-addrs $msg]]]
          
            1
          

email:bcc-addrs

[$email:bcc-addrs] -> [sequence element] — Return all Bcc addresses, expanding any RFC 5322 groups.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: c@y.com\r\nBcc: a@x.com, b@x.com\r\n\r\nbody"]]]
  [$count [$email:bcc-addrs $msg]]]
          
            2
          

email:date-header

[$email:date-header] -> datetime — Return the Date header parsed from RFC 5322 form into a datetime.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "Date: Tue, 26 May 2026 14:30:00 +0000\r\nFrom: a@b.com\r\n\r\nbody"]]]
  [$email:date-header $msg]]
          
            '2026-05-26T14:30:00Z'
          

email:message-id

[$email:message-id] -> string — Return the Message-ID value with the surrounding angle brackets stripped.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "Message-ID: \r\nFrom: a@b.com\r\n\r\nbody"]]]
  [$email:message-id $msg]]
          
            'abc123@example.com'
          

email:in-reply-to

[$email:in-reply-to] -> string — Return the In-Reply-To value with the surrounding angle brackets stripped.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "In-Reply-To: \r\nFrom: a@b.com\r\n\r\nbody"]]]
  [$email:in-reply-to $msg]]
          
            'def456@example.com'
          

email:references

[$email:references] -> [sequence string] — Return the References threading chain as a sequence of message IDs.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "References:  \r\nFrom: c@y.com\r\n\r\nbody"]]]
  [$count [$email:references $msg]]]
          
            2
          

email:list-unsubscribe

[$email:list-unsubscribe] -> [sequence string] — Return the URIs from the List-Unsubscribe header as a sequence.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "List-Unsubscribe: , \r\nFrom: a@b.com\r\n\r\nbody"]]]
  [$count [$email:list-unsubscribe $msg]]]
          
            2
          

email:parts

[$email:parts] -> [sequence element] — Return all body parts flattened (a single part for non-multipart messages).

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: a@b.com\r\nContent-Type: text/plain\r\n\r\njust text"]]]
  [$count [$email:parts $msg]]]
          
            1
          

email:attachments

[$email:attachments] -> [sequence element] — Return parts marked as attachments, each carrying filename, content-type, and decoded bytes.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: a@b.com\r\nContent-Type: multipart/mixed; boundary=\"B\"\r\n\r\n--B\r\nContent-Type: text/plain\r\n\r\nhi\r\n--B\r\nContent-Type: application/pdf\r\nContent-Disposition: attachment; filename=\"invoice.pdf\"\r\n\r\nPDF\r\n--B\r\nContent-Type: image/png\r\nContent-Disposition: attachment; filename=\"img.png\"\r\n\r\nPNG\r\n--B--\r\n"]]]
  [$count [$email:attachments $msg]]]
          
            2
          

email:text-body

[$email:text-body] -> string — Return the first text/plain part decoded to UTF-8, or empty if absent.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: a@b.com\r\nContent-Type: multipart/alternative; boundary=\"B\"\r\n\r\n--B\r\nContent-Type: text/plain\r\n\r\nplain text here\r\n--B\r\nContent-Type: text/html\r\n\r\n

html here

\r\n--B--\r\n"]]] [$email:text-body $msg]]
            'plain text here'
          

email:html-body

[$email:html-body] -> string — Return the first text/html part decoded to UTF-8 — raw and unsanitized; sanitize before rendering.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: a@b.com\r\nContent-Type: multipart/alternative; boundary=\"B\"\r\n\r\n--B\r\nContent-Type: text/plain\r\n\r\nplain text here\r\n--B\r\nContent-Type: text/html\r\n\r\n

html here

\r\n--B--\r\n"]]] [$email:html-body $msg]]
            '

html here

'

email:is-multipart

[$email:is-multipart] -> bool — Report whether the message body is a multipart tree rather than a single part.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: a@b.com\r\nTo: c@d.com\r\nSubject: Hi\r\n\r\nHello body\r\n"]]]
  [$email:is-multipart $msg]]
          
            false
          

email:decode-encoded-word

[$email:decode-encoded-word] -> string — Decode an RFC 2047 encoded-word (Q or B) from its declared charset to UTF-8.

            [?lib 'cx-stdlib/email']
[$email:decode-encoded-word "=?UTF-8?Q?Hello_=E4=B8=96=E7=95=8C?="]
          
            'Hello 世界'
          

email:encode-encoded-word

[$email:encode-encoded-word] -> string — Encode a string as an RFC 2047 encoded-word using the given charset and Q or B encoding.

            [?lib 'cx-stdlib/email']
[$email:encode-encoded-word "Hi" "UTF-8" "Q"]
          
            '=?UTF-8?Q?Hi?='
          

email:parse-address

[$email:parse-address] -> element — Parse a single address string into an [address] element.

            [?lib 'cx-stdlib/email']
[$email:format-address [$email:parse-address "John Doe "]]
          
            'John Doe '
          

email:parse-address-list

[$email:parse-address-list] -> [sequence element] — Parse an address list into a sequence mixing [address] and [address-group] elements.

            [?lib 'cx-stdlib/email']
[$count [$email:parse-address-list "alice@x.com, bob@y.com"]]
          
            2
          

email:format-address

[$email:format-address] -> string — Format an [address] element back to its RFC 5322 mailbox string.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: John Doe \r\n\r\nbody"]]]
  [$email:format-address [$email:from-addr $msg]]]
          
            'John Doe '
          

email:format-address-list

[$email:format-address-list] -> string — Format a sequence of addresses and groups into an RFC 5322 address-list string.

            [?lib 'cx-stdlib/email']
[$email:format-address-list [$email:parse-address-list "team: alice@x, bob@x;"]]
          
            'team: alice@x, bob@x;'
          

email:build

[$email:build] -> element — Build a plaintext [message] from a parts map (from, to, subject, body, plus optional headers).

            [?lib 'cx-stdlib/email']
[?let [= $msg [$email:build {"from": "agent@example.com",
                            "to": "customer@example.com",
                            "subject": "Re: Invoice question",
                            "body": "Thank you for reaching out..."}]]
  [$email:subject [$email:parse [$email:emit $msg]]]]
          
            'Re: Invoice question'
          

email:build-multipart

[$email:build-multipart] -> element — Build a multipart [message] from a parts map plus a sequence of alternative part elements.

            [?lib 'cx-stdlib/email']
[?let [= $msg [$email:build-multipart {"from": "a@b.com", "to": "c@d.com", "subject": "x", "body": "plain"}
                ([part content-type="text/html" body="

rich

"])]] [$email:is-multipart $msg]]
            true
          

email:reply

[$email:reply] -> element — Build a reply to a message, preserving threading and prefixing the subject with Re:.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $orig [$email:parse [$bytes:from-string-utf8 "From: sender@x.com\r\nMessage-ID: \r\nSubject: Question\r\n\r\nbody"]]]
  [= $r [$email:reply $orig {"body": "Answer."}]]
  [$email:in-reply-to $r]]
          
            'abc123@example.com'
          

email:forward

[$email:forward] -> element — Build a forward that wraps the original as a message/rfc822 attachment, prefixing the subject with Fwd:.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $orig [$email:parse [$bytes:from-string-utf8 "From: sender@x.com\r\nSubject: Report\r\n\r\nbody"]]]
  [= $f [$email:forward $orig {"to": "dest@x.com", "body": "FYI"}]]
  [$email:subject $f]]
          
            'Fwd: Report'
          

email:parse-dsn

[$email:parse-dsn] -> element — Lift an already-parsed delivery-status report into a structured [dsn] element.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: MAILER-DAEMON@mx\r\nContent-Type: multipart/report; report-type=delivery-status; boundary=\"B\"\r\n\r\n--B\r\nContent-Type: text/plain\r\n\r\nfailed\r\n--B\r\nContent-Type: message/delivery-status\r\n\r\nReporting-MTA: dns; mail.example.com\r\n\r\nFinal-Recipient: rfc822; user@example.com\r\nAction: failed\r\nStatus: 5.1.1\r\nDiagnostic-Code: smtp; 550 no such user\r\n--B--\r\n"]]]
  [= $dsn [$email:parse-dsn $msg]]
  [$email:is-bounce $dsn]]
          
            true
          

email:parse-dsn-bytes

[$email:parse-dsn-bytes] -> element — Parse raw bytes and lift the delivery-status report into a structured [dsn] element.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $dsn [$email:parse-dsn-bytes [$bytes:from-string-utf8 "From: MAILER-DAEMON@mx\r\nContent-Type: multipart/report; report-type=delivery-status; boundary=\"B\"\r\n\r\n--B\r\nContent-Type: text/plain\r\n\r\nfailed\r\n--B\r\nContent-Type: message/delivery-status\r\n\r\nReporting-MTA: dns; mail.example.com\r\n\r\nFinal-Recipient: rfc822; user@example.com\r\nAction: failed\r\nStatus: 5.1.1\r\nDiagnostic-Code: smtp; 550 no such user\r\n--B--\r\n"]]]
  [$email:is-bounce $dsn]]
          
            true
          

email:is-bounce

[$email:is-bounce] -> bool — Report whether any recipient in the DSN failed or was delayed.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: MAILER-DAEMON@mx\r\nContent-Type: multipart/report; report-type=delivery-status; boundary=\"B\"\r\n\r\n--B\r\nContent-Type: text/plain\r\n\r\nfailed\r\n--B\r\nContent-Type: message/delivery-status\r\n\r\nReporting-MTA: dns; mail.example.com\r\n\r\nFinal-Recipient: rfc822; user@example.com\r\nAction: failed\r\nStatus: 5.1.1\r\nDiagnostic-Code: smtp; 550 no such user\r\n--B--\r\n"]]]
  [= $dsn [$email:parse-dsn $msg]]
  [$email:is-bounce $dsn]]
          
            true
          

email:is-hard-bounce

[$email:is-hard-bounce] -> bool — Report whether any recipient failed permanently with a 5.x.x status.

            [?lib 'cx-stdlib/email']
[?lib 'cx-stdlib/bytes']
[?let [= $msg [$email:parse [$bytes:from-string-utf8 "From: MAILER-DAEMON@mx\r\nContent-Type: multipart/report; report-type=delivery-status; boundary=\"B\"\r\n\r\n--B\r\nContent-Type: text/plain\r\n\r\nfailed\r\n--B\r\nContent-Type: message/delivery-status\r\n\r\nReporting-MTA: dns; mail.example.com\r\n\r\nFinal-Recipient: rfc822; user@example.com\r\nAction: failed\r\nStatus: 5.1.1\r\nDiagnostic-Code: smtp; 550 no such user\r\n--B--\r\n"]]]
  [= $dsn [$email:parse-dsn $msg]]
  [$email:is-hard-bounce $dsn]]
          
            true