Skip to content

Repository files navigation

Typescript Email Parser

Parse emails into typed objects, that can be used in your typescript code (Github | npm)

Parsing is done according to the RFC5322 specification, see grammar.

Table of Contents:

Installation

Install the project using npm

npm install --save-dev typescript-email-parser

Features

This project is a library for converting emails into typed object that can be used by code. It supports Typescript.

It is a complete implementation of the RFC5322 standard, for the specification of the structure of internet messages (i.e. emails), including the obsolete ("legacy") grammar from § 4.

In addition to the strict RFC5322 header grammar, the library parses message bodies into their full MIME structure (RFC2045–RFC2049): nested multipart trees, base64 and quoted-printable transfer encodings, charset decoding (including RFC2047 encoded words and RFC2231 parameters), nested message/rfc822 parts, and attachment/body classification. Headers are parsed into typed values — addresses, dates and Received included.

The API and behavior follow Stalwart Labs' mail-parser, and are tested against mail-parser's own test data: all 107 messages in its corpus match exactly, on both the parsed MIME structure and every parsed header value, along with its address, date, Received and Content-Type field test sets.

How to use

Email.parse takes a message as bytes, a string, or a readable byte stream, and returns an Email — the parsed message, headers and MIME structure together. It is tolerant: it accepts any line endings and 8-bit content, and returns undefined only when the input contains no headers at all.

import { Email } from 'typescript-email-parser';
import { readFileSync } from 'fs';

let email = Email.parse(readFileSync('./hello.eml'))!

console.log(`Re: ${email.subject()}`)
console.log(email.body_text(0))

Typed header accessors:

email.from()        // Address: { kind: 'list', value: Mailbox[] } or { kind: 'group', value: Group[] }
email.to()
email.cc()
email.bcc()
email.reply_to()
email.sender()
email.date()        // DateTime
email.subject()     // string
email.message_id()  // string
email.received_all()// Received[]: from / by / for_ / with / tls_version / id / helo / date ...

email.header('X-Custom')     // any header, by wire name or canonical id
email.header_values('comments')
email.headers()              // every header, in order

Mailbox is { name?: string, address?: string }both optional, because real messages contain From: Whomever with a display name and no address. Group is { name?: string, addresses: Mailbox[] }. DateTime stores numbers and converts without reparsing:

email.date()?.to_date()      // a JavaScript Date
email.date()?.to_rfc3339()   // '2023-06-20T20:28:11+02:00'
email.date()?.to_timestamp() // seconds since the Unix epoch

Parsing from a stream

parse also accepts a readable byte stream, which is how runtimes such as Cloudflare Email Workers deliver mail. Given a stream it returns a promise, because reading a stream is genuinely asynchronous; given bytes or a string it returns the Email directly:

Email.parse(bytes)          // Email | undefined
Email.parse(string)         // Email | undefined
await Email.parse(stream)   // Email | undefined
export default {
  async email(message, env, ctx) {
    let email = await Email.parse(message.raw, { maxBytes: 25 * 1024 * 1024 })
    console.log(email?.body_text(0))
  }
}

A stream is read to completion and then parsed — it is not consumed incrementally, and there is no separate parseStream method precisely because that name would imply otherwise. The parser needs random access to the whole message: it backtracks, hands out views into the raw bytes, and records absolute part offsets. See docs/streaming.md for the full reasoning, what an incremental parser would involve, and what to do if your concern is blocking rather than memory.

Bytes and strings

A message is bytes; a JavaScript string is UTF-16 text. Passing a Uint8Array or Buffer is unambiguous and is the recommended path.

String input is converted using the encoding option, which defaults to 'latin1' — one character per byte, so a file read with readFileSync(path, 'latin1') round-trips exactly. A character above U+00FF cannot be a single byte and throws EmailEncodingError rather than being silently truncated:

Email.parse(readFileSync('./hello.eml'))                     // bytes: unambiguous
Email.parse(readFileSync('./hello.eml', 'latin1'))           // latin1 string: exact
Email.parse('Subject: café\r\n\r\nbody\r\n', { encoding: 'utf-8' })  // text: encode first

Limiting message size

Attachment sizes are attacker-controlled, so every parse entry point takes an optional byte limit. Exceeding it throws EmailTooLargeError.

import { Email, EmailTooLargeError } from 'typescript-email-parser'

const GMAIL_LIMIT = 25 * 1024 * 1024

try {
  let email = Email.parse(raw, { maxBytes: GMAIL_LIMIT })
} catch (error) {
  if (error instanceof EmailTooLargeError) {
    console.warn(`rejected ${error.size} bytes, limit is ${error.maxBytes}`)
  }
}

When parsing a stream the limit is enforced while reading and cancels the stream as soon as it is exceeded, so an oversized message is never fully buffered just to be rejected afterwards:

let email = await Email.parse(message.raw, { maxBytes: GMAIL_LIMIT })

If you only need the headers, parseHeaders stops at the blank line ending the header block and never decodes the body — on a 25 MB message that is 0.2 ms versus 581 ms for a full parse.

Keeping large messages off the event loop

Parsing is CPU-bound and synchronous. A 25 MB attachment takes ~580 ms, and in Node that blocks the event loop for the whole duration — every other request on the process waits. There is deliberately no async parse for this: wrapping synchronous work in a promise defers it by a microtask without yielding, so callers would await and feel safe while the process still stalls.

Three things actually help, cheapest first.

Parse only what you need. If you are routing or filtering on headers, never decode the body:

let headers = Email.parseHeaders(raw)!   // 0.2 ms on a 25 MB message
if (headers.subject()?.startsWith('[spam]')) return

Cap the input. maxBytes turns an unbounded stall into a rejection you control.

Move the work to a worker thread. This is the real fix for a server handling large mail — it addresses throughput, not just latency, and needs nothing from this library:

// parse-worker.ts
import { parentPort } from 'worker_threads'
import { Email } from 'typescript-email-parser'

parentPort!.on('message', (raw: Uint8Array) => {
  let email = Email.parse(raw, { maxBytes: 25 * 1024 * 1024 })
  // Post back a plain summary: an Email holds views into `raw_message`, and
  // structured-cloning the whole tree would copy the message a second time
  parentPort!.postMessage({
    subject: email?.subject(),
    from: email?.from(),
    text: email?.body_text(0),
    attachments: email?.attachment_parts().map(p => p.attachment_name()),
  })
})
// server.ts
import { Worker } from 'worker_threads'
import { join } from 'path'

let worker = new Worker(join(__dirname, 'parse-worker.js'))
let parsed = await new Promise(resolve => {
  worker.once('message', resolve)
  worker.postMessage(raw, [raw.buffer])   // transfer, don't copy
})

Passing [raw.buffer] as a transfer list hands the bytes over instead of cloning them, so a 25 MB message costs no extra copy — at the price of raw being detached in the sender afterwards. Drop the transfer list if you still need the bytes on the main thread. (Under ESM, resolve the worker path with new URL('./parse-worker.js', import.meta.url) instead of __dirname.)

Cloudflare Workers has no worker_threads and applies CPU limits regardless, so there parseHeaders and maxBytes are the tools available. See docs/streaming.md for why incremental parsing would not solve this either — the same total work still happens on the same thread.

Strict RFC5322 validation

Email.parseStrict additionally runs the full RFC5322 PEG grammar, including the obsolete syntax of section 4, and returns undefined when the message does not conform:

Email.parse(lfOnlyMessage)        // an Email — the MIME parser is line-ending tolerant
Email.parseStrict(lfOnlyMessage)  // undefined — RFC5322 requires CRLF

It is roughly 130× slower than parse (7.4 ms vs 0.056 ms on a 2 KB message), so it is not on the default path. Use it when you need to validate, not merely to read. It also populates email.prepended, the trace/resent field blocks — though note the grammar requires trace fields to be contiguous at the start of the header block, which real messages rarely satisfy, so it is usually empty. For the Received headers themselves, use received_all().

The MIME structure

The Email is the message — the body structure is on the same object. The API mirrors stalwartlabs/mail-parser:

let message = Email.parse(readFileSync('./hello.eml'))!

message.body_text(0)                  // decoded plain text body (converted from HTML if necessary)
message.body_html(0)                  // decoded HTML body (converted from text if necessary)
message.attachment_count()            // number of attachments
message.attachment(0)?.attachment_name()
message.attachment(0)?.content_type()
message.parts                         // all MIME parts; part ids in html_body,
                                      // text_body and attachments index into it

message.raw_body()                    // undecoded body bytes (a view into raw_message)
message.raw_body_text()               // the same bytes as a string

The raw body is reached through message.raw_body(), which is a view into the bytes the parser already holds rather than a second copy. It is line-ending and 8-bit tolerant, so it resolves even for messages the strict RFC5322 grammar cannot parse.

Notes:

  • Part offsets always refer to the raw bytes, so raw_body() and header_raw() are views rather than copies.
  • htmlToText supports the common named HTML entities plus all numeric character references.
  • Aside from EmailTooLargeError and EmailEncodingError, both of which you opt into, parsing never throws.

FAQ

Some commonly asked questions:

Email not parsing

I'm always getting undefined when I parse a valid email

The parser will parse most emails correctly, however there are two situations where an email can seem correct but is not.

  1. Not using CRLF as line breaks.
  2. The email MUST end in a CRLF line break.

A common way you can get this error is by copying a plain text email into an editor that is configured for that specific file to use \n, such as in your test code. What you can do is just replace \n with \r\n and it should work.

Also, as mentioned in point 2, that it's common for the the last CRLF to be left off when copying an email. If you add a CRLF line break at the end then you should be fine.

Note that this applies only to Email.parseStrict, which validates against the RFC5322 grammar. Email.parse is line-ending tolerant and handles LF-only messages correctly — if parseStrict returns undefined but you still want the content, use parse.

Contributing

This is a new project and contributions are always welcome, just file an issue or open a PR. Things that are needed:

  • More testing
  • Full mail-parser parity for header values inside the MIME layer (typed address, date and Received values)
  • A streaming parser — see docs/streaming.md for the findings and what it would take
  • Refinement of the "rewriter" and that whole process

The project is maintained by Spence. Feel free to open an issue, or contact him directly.

The Grammar

The following EBNF style grammar describes formally how emails are parsed.

// RFC 5322
// See: https://datatracker.ietf.org/doc/html/rfc5322
// The following is the RFC5322 "Internet Message Format" specification
// using tspeg to represent the grammar and to generate a parser in TS
// This file is intended purely to document (without computed fields)

// "Core Rules" from RFC5234
// See: https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1
CR              :=    '\x0D'              // carriage return, i.e. '\r'
CRLF            :=    '\r\n'              // Internet standard newline
DIGIT           :=    '\d'                // 0-9
TWO_DIGIT       :=    '\d\d'              // 00-99
FOUR_DIGIT      :=    '\d\d\d\d'          // 0000-9999
DQUOTE          :=    '\x22'              // double quote, i.e. '"'
HTAB            :=    '\x09'              // horizontal tab, i.e. 'TAB'
LF              :=    '\x0A'              // linefeed, i.e. '\n'
SP              :=    '\x20'              // space, i.e. 'Space'
VCHAR           :=    '[\x21-\x7E]'       // visible (printing) characters
WSP             :=    SP | HTAB           // white space

// § 3.2.1 Quoted Characters
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.2.1
quoted_pair     :=    '\\' { VCHAR | WSP }  | obs_qp

// §3.2.2 Folding White Space and Comments
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.2.2
FWS             :=    { WSP* CRLF }? WSP+ |  obs_FWS
ctext           :=    '[\x21-\x27]' | '[\x2a-\x5b]' | '[\x5d-\x7e]' | obs_ctext
ccontent        :=    ctext | quoted_pair | comment
comment         :=    '\(' { FWS? ccontent }* FWS? '\)'
CFWS            :=    { FWS? comment }+ FWS? | FWS

// § 3.2.3 Atom
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.2.3
// (Printable US-ASCII characters not including specials. Used for atoms.)
atext           :=    '[A-Za-z0-9!#$%&\x27\*\+\-\/=?^_`{|}~]'
atom            :=    CFWS? atext+ CFWS?
dot_atom_text   :=    atext+ { '\.' atext+ }*
dot_atom        :=    CFWS? dot_atom_text CFWS?

// (Special characters that do not appear in atext)
specials        :=     '\(' |'\)' | '[<>]' | '\[' | '\]' | '[:;@]' | '\\' | ',' | '\.' | DQUOTE

// § 3.2.4 Quoted Strings
// See https://datatracker.ietf.org/doc/html/rfc5322#section-3.2.4
// (Printable US-ASCII characters not including '"' or the quote character)
qtext           :=    '\x21' | '[\x23-\x5b]' | '[\x5d-\x7e]' | obs_qtext
qcontent        :=    qtext | quoted_pair
quoted_string   :=    CFWS? DQUOTE { FWS? qcontent }* FWS? DQUOTE CFWS?

// § 3.2.5 Miscellaneous Tokens
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.2.5
word            :=    atom | quoted_string
phrase          :=    word+ | obs_phrase
unstructured    :=    { FWS? VCHAR }* WSP* | obs_unstruct

// § 3.3.  Date and Time Specification
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.3
date_time       :=    { day_of_week ',' }? date time CFWS?
day_of_week     :=    FWS? day_name | obs_day_of_week
day_name        :=    'Mon' | 'Tue' | 'Wed' | 'Thu' | 'Fri' | 'Sat' | 'Sun'
date            :=    day month year
day             :=    FWS? DIGIT DIGIT? FWS | obs_day
month           :=    'Jan' | 'Feb' | 'Mar' | 'Apr' |
                      'May' | 'Jun' | 'Jul' | 'Aug' |
                      'Sep' | 'Oct' | 'Nov' | 'Dec'
year            :=    FWS FOUR_DIGIT DIGIT* FWS | obs_year
time            :=    time_of_day zone
time_of_day     :=    hour ':' minute { ':' second }?
hour            :=    TWO_DIGIT | obs_hour
minute          :=    TWO_DIGIT | obs_minute
second          :=    TWO_DIGIT | obs_second
zone            :=    FWS { '\+' | '\-' } FOUR_DIGIT | obs_zone

// § 3.4 Address Specification
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.4
address         :=    mailbox | group
mailbox         :=    name_addr | addr_spec
name_addr       :=    display_name? angle_addr
angle_addr      :=    CFWS? '<' addr_spec '>' CFWS? | obs_angle_addr
group           :=    display_name ':' group_list? ';' CFWS?
display_name    :=    phrase
mailbox_list    :=    mailbox { ',' mailbox }* | obs_mbox_list
address_list    :=    address { ',' address }* | obs_addr_list
group_list      :=    mailbox_list | CFWS | obs_group_list

// § 3.4.1 Addr-Spec Specification
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.4.1
addr_spec       :=    local_part '@' domain
local_part      :=    dot_atom | quoted_string | obs_local_part
domain          :=    dot_atom | domain_literal | obs_domain
domain_literal  :=    CFWS? '\[' { FWS? dtext }* FWS? '\]' CFWS?
// (Printable US-ASCII characters not including '[', ']', or '\"')
dtext           :=    '[\x21-\x5a]' | '[\x5e-\x7e]' | obs_dtext

// § 3.5 Overall Message Syntax
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.5
message         :=    { fields | obs_fields } { CRLF body }?
body            :=    { _998text CRLF }* _998text | obs_body
text            :=    '[\x01-\x09]' | '\x0B' | '\x0C' | '[\x0E-\x7f]' // Characters excluding CR and LF
_998text        :=    '[\x01-\x09\x0B\x0C\x0E-\x7F]{0,998}'            // Note: _998text to workaround tspeg operator

// § 3.6 Field Definitions
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.6
fields          :=    {
                        trace optional_field* |
                        { resent_date | resent_from | resent_sender | resent_to | resent_cc | resent_bcc | resent_msg_id }*
                      }*
                      { orig_date | from | sender | reply_to | to | cc | bcc | message_id | in_reply_to | references | subject | comments | keywords | optional_field }*

// § 3.6.1. The Origination Date Field
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.1
orig_date       :=   'Date' ':' date_time CRLF

// § 3.6.2 Originator Fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2
from            :=    'From' ':' mailbox_list CRLF
sender          :=    'Sender' ':' mailbox CRLF
reply_to        :=    'Reply-To' ':' address_list CRLF

// § 3.6.3 Destination Address Fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3
to              :=    'To' ':' address_list CRLF
cc              :=    'Cc' ':' address_list CRLF
bcc             :=    'Bcc' ':' { address_list | CFWS }? CRLF

// § 3.6.4 Identification Fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.4
message_id      :=    'Message-ID' ':' msg_id CRLF
in_reply_to     :=    'In-Reply-To' ':' msg_id+ CRLF
references      :=    'References' ':' msg_id+ CRLF
msg_id          :=    CFWS? '<' id_left '@' id_right '>' CFWS?
id_left         :=    dot_atom_text | obs_id_left
id_right        :=    dot_atom_text | no_fold_literal | obs_id_right
no_fold_literal :=    '\[' dtext* '\]'

// § 3.6.5 Informational Fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.5
subject         :=    'Subject' ':' unstructured CRLF
comments        :=    'Comments' ':' unstructured CRLF
keywords        :=    'Keywords' ':' phrase { ',' phrase }* CRLF

// § 3.6.6 Resent Fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.6
resent_date     :=    'Resent-Date' ':' date_time CRLF
resent_from     :=    'Resent-From' ':' mailbox_list CRLF
resent_sender   :=    'Resent-Sender' ':' mailbox CRLF
resent_to       :=    'Resent-To' ':' address_list CRLF
resent_cc       :=    'Resent-Cc' ':' address_list CRLF
resent_bcc      :=    'Resent-Bcc' ':' {address_list | CFWS }? CRLF
resent_msg_id   :=    'Resent-Message-ID' ':' msg_id CRLF

// § 3.6.7 Trace Fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.7
trace           :=    return_path? received+
return_path     :=    'Return-Path' ':' path CRLF
path            :=    angle_addr | CFWS? '<' CFWS '>' CFWS?
received        :=    'Received' ':' received_token* ';' date_time CRLF
received_token  :=    angle_addr | addr_spec | domain | word

// § 3.6.8 Optional Fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.8
optional_field  :=    field_name ':' unstructured CRLF
field_name      :=    ftext+
ftext           :=    '[\x21-\x39]' |   // ; Printable US-ASCII
                      '[\x3b-\x7e]'     // ;  characters not including  ":".

// § 4.1 Miscellaneous Obsolete Tokens
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-4.1
// (US-ASCII control characters that do not include the carriage return, line feed, and white space characters)
obs_NO_WS_CTL   :=    '[\x01-\x08]' | '\x0B' | '\x0C' | '[\x0E-\x1F]' | '\x7F'
obs_ctext       :=    obs_NO_WS_CTL
obs_qtext       :=    obs_NO_WS_CTL
obs_utext       :=    '\x00' | obs_NO_WS_CTL | VCHAR
obs_qp          :=    '\\' { '\x00' | obs_NO_WS_CTL | LF | CR }
obs_body        :=    { { LF* CR* { { '\x00' | text } LF* CR* }* } | CRLF }*
obs_unstruct    :=    { { LF* CR* { obs_utext LF* CR* }* } | FWS}*
obs_phrase      :=    word { word | '.' | CFWS }
obs_phrase_list :=    { phrase | CFWS } { ',' { phrase | CFWS }? }*

// § 4.2 Obsolete Folding White Space
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-4.2
obs_FWS         :=    WSP+ { CRLF WSP+ }*

// § 4.3. Obsolete Date and Time
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-4.3
obs_day_of_week :=    CFWS? day_name CFWS?
obs_day         :=    CFWS? DIGIT DIGIT? CFWS?
obs_year        :=    CFWS? TWO_DIGIT DIGIT* CFWS?
obs_hour        :=    CFWS? TWO_DIGIT CFWS?
obs_minute      :=    CFWS? TWO_DIGIT CFWS?
obs_second      :=    CFWS? TWO_DIGIT CFWS?
obs_zone        :=    'UT' | 'GMT' |  // ; Universal Time
                                      // ; North American UT
                                      // ; offsets
                      'EST' | 'EDT' | // ; Eastern:  - 5| - 4
                      'CST' | 'CDT' | // ; Central:  - 6| - 5
                      'MST' | 'MDT' | // ; Mountain: - 7| - 6
                      'PST' | 'PDT' | // ; Pacific:  - 8| - 7
                      '[\x41-\x49]' | // ; Military zones - "A"
                      '[\x4b-\x5a]' | // ; through "I" and "K"
                      '[\x61-\x69]' | // ; through "Z", both
                      '[\x6b-\x7a]'   // ; upper and lower case

// § 4.4 Obsolete Addressing
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-4.4
obs_angle_addr  :=    CFWS? '<' obs_route addr_spec '>' CFWS?
obs_route       :=    obs_domain_list ':'
obs_domain_list :=    { CFWS | ',' }* '@' domain { ',' CFWS? { '@' domain }? }*
obs_mbox_list   :=    { CFWS? ',' }* mailbox { ',' { mailbox | CFWS }? }*
obs_addr_list   :=    { CFWS? ',' }* address { ',' { address | CFWS }? }*
obs_group_list  :=    { CFWS? ',' }+ CFWS?
obs_local_part  :=    word { '\.' word }*
obs_domain      :=    atom { '\.' atom }*
obs_dtext       :=    obs_NO_WS_CTL | quoted_pair

// § 4.5 Obsolete Header Fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-4.5
obs_fields      :=    { obs_return | obs_received | obs_orig_date | obs_from | obs_sender |
                        obs_reply_to | obs_to | obs_cc | obs_bcc | obs_message_id | obs_in_reply_to |
                        obs_references | obs_subject | obs_comments | obs_keywords | obs_resent_date |
                        obs_resent_from | obs_resent_send | obs_resent_rply | obs_resent_to | obs_resent_cc |
                        obs_resent_bcc | obs_resent_mid | obs_optional }*

// § 4.5.1.  Obsolete Origination Date Field
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-4.5.1
obs_orig_date   :=    'Date' WSP* ':' date_time CRLF

// § 4.5.2.  Obsolete Originator Fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-4.5.2
obs_from        :=    'From' WSP* ':' mailbox_list CRLF
obs_sender      :=    'Sender' WSP* ':' mailbox CRLF
obs_reply_to    :=    'Reply-To' WSP* ':' address_list CRLF

// § 4.5.3.  Obsolete Destination Address Fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-4.5.3
obs_to          :=    'To' WSP* ':' address_list CRLF
obs_cc          :=    'Cc' WSP* ':' address_list CRLF
obs_bcc         :=    'Bcc' WSP* ':' { address_list | { CFWS? ',' }* CFWS? } CRLF

// § 4.5.4.  Obsolete Identification Fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-4.5.4
obs_message_id  :=    'Message-ID' WSP* ':' msg_id CRLF
obs_in_reply_to :=    'In-Reply-To' WSP* ':' { phrase | msg_id }* CRLF
obs_references  :=    'References' WSP* ':' { phrase | msg_id }* CRLF
obs_id_left     :=    local_part
obs_id_right    :=    domain

// § 4.5.5.  Obsolete Informational Fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-4.5.5
obs_subject     :=    'Subject'   WSP* ':' unstructured     CRLF
obs_comments    :=    'Comments'  WSP* ':' unstructured     CRLF
obs_keywords    :=    'Keywords'  WSP* ':' obs_phrase_list  CRLF

// § 4.5.6.  Obsolete Resent Fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-4.5.6
// The obsolete syntax adds a 'Resent-Reply-To:' field, which consists of the field name, the optional comments and folding white space, the colon, and a comma separated list of addresses.
obs_resent_from :=    'Resent-From'       WSP* ':' mailbox_list CRLF
obs_resent_send :=    'Resent-Sender'     WSP* ':' mailbox      CRLF
obs_resent_date :=    'Resent-Date'       WSP* ':' date_time    CRLF
obs_resent_to   :=    'Resent-To'         WSP* ':' address_list CRLF
obs_resent_cc   :=    'Resent-Cc'         WSP* ':' address_list CRLF
obs_resent_bcc  :=    'Resent-Bcc'        WSP* ':' { address_list | { CFWS? ',' }* CFWS? } CRLF
obs_resent_mid  :=    'Resent-Message-ID' WSP* ':' msg_id CRLF
obs_resent_rply :=    'Resent-Reply-To'   WSP* ':' address_list CRLF

// As with other resent fields, the 'Resent-Reply-To:' field is to be treated as trace information only.

// § 4.5.7.  Obsolete Trace Fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-4.5.7
//The obs_return and obs_received are again given here as template definitions, just as return and received are in section 3.  Their full syntax is given in [RFC5321].
obs_return      :=    'Return-Path'       WSP* ':' path CRLF
obs_received    :=    'Received'          WSP* ':' received_token* CRLF

// § 4.5.8.  Obsolete optional fields
// See: https://datatracker.ietf.org/doc/html/rfc5322#section-4.5.8
obs_optional    :=    field_name WSP* ':' unstructured CRLF

About

Parse rfc5322 email headers into typed objects

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages