CX · Reference guideemail
PlaygroundDownloadsAboutv0.18.0-pre.1

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 <john@example.com>\r\n\r\nbody"]]]
  [$email:format-address [$email:from-addr $msg]]]
    
      'John Doe <john@example.com>'
    

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: <abc123@example.com>\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: <def456@example.com>\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: <a@x> <b@x>\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: <https://x/u>, <mailto:u@x>\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<p>html here</p>\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<p>html here</p>\r\n--B--\r\n"]]]
  [$email:html-body $msg]]
    
      '<p>html here</p>'
    

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@example.com>"]]
    
      'John Doe <john@example.com>'
    

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 <john@example.com>\r\n\r\nbody"]]]
  [$email:format-address [$email:from-addr $msg]]]
    
      'John Doe <john@example.com>'
    

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="<p>rich</p>"])]]
  [$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: <abc123@example.com>\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