Documentation Index: Fetch llms.txt first to discover every published page. This page is also available as Markdown at /node-sdk-parsing-email/node-sdk-address-parsing.md.
Verified · 8/11/2026

Raw MIME Address Parsing (parser/address)

Reference for parseFromHeader and parseFromHeaderLoose, the two RFC 5322 address parsers in @primitivedotdev/sdk/parser that extract a single validated address from a From/Sender header.

What the address parsers do#

Two address parsers ship under @primitivedotdev/sdk/parser: parseFromHeader, a strict RFC 5322 parser meant for authorization decisions, and parseFromHeaderLoose, a lenient parser meant for display-only call sites. Both operate on a raw header string (e.g. the From or Sender header value) and are implemented in sdk-node/src/parser/address-parser.ts.

Warning

Never use parseFromHeaderLoose for permission gates or any decision that grants access. Its recovered display name can silently absorb a second address or garbage tokens. Use parseFromHeader for anything security-bearing.

parseFromHeader#

Strictly parses a header for security-bearing contexts: allowlist gates, permission grants, domain-anchored sender trust anchors, and similar decisions where the address itself is the thing being trusted.

import { parseFromHeader } from "@primitivedotdev/sdk/parser";

const result = parseFromHeader('"Alice" <alice@example.com>');

if (result.ok) {
  console.log(result.value.address); // "alice@example.com"
} else {
  console.log(result.reason); // one of ParseFromHeaderFailureReason
}

Signature#

function parseFromHeader(
  header: string | null | undefined,
): ParseFromHeaderResult;

Result shape#

type ParseFromHeaderResult =
  | { ok: true; value: ValidatedAddress }
  | { ok: false; reason: ParseFromHeaderFailureReason };

interface ValidatedAddress {
  address: string; // lowercased local-part and domain
}

Rejection reasons#

parseFromHeader never falls back to a "best guess." Any of the following inputs return { ok: false, reason } instead of a partially-parsed address:

reasonTrigger
emptyheader is null, undefined, or whitespace-only after trimming
too_longHeader exceeds 998 UTF-8 bytes (the RFC 5322 §2.1.1 line limit, measured in bytes so multi-byte SMTPUTF8/RFC 6531 characters can't bypass the cap)
multiple_addressesHeader contains more than one address (RFC 5322 permits multi-address From, but it's rare and ambiguous as an identity)
group_syntaxHeader uses group syntax, e.g. Friends: a@b.com, c@d.com;
invalid_addressThe single address fails validator's isEmail check under the SDK's chosen options, or the parse produced no usable entry

The isEmail check (via validator/lib/isEmail) enforces per-part length limits (64-octet local-part, 255-octet domain), dot-atom rules, hostname-label rules, a required TLD, and other RFC 5321/5322 conformance checks. Its options:

OptionValueEffect
allow_ip_domaintrueAccepts address-literals like user@[192.168.1.1]
require_tldtrueRejects user@localhost
allow_display_namefalseOnly the bare addr-spec is checked (the display name was already stripped by the header parser)
allow_utf8_local_parttrueAccepts SMTPUTF8 / EAI local-parts

Normalization#

On success, value.address is lowercased in full, both the local-part and the domain. RFC 5321 §2.4 technically permits case-sensitive local-parts, but every consumer mailbox treats them as case-insensitive in practice; a case-sensitive grant key would otherwise split Bob@x.com from bob@x.com into separate rows and defeat a primary-key lookup.

No display name is returned. Deliberately: an input like Name <user@x.com> <attacker@y.com> parses as one entry whose name field can silently absorb the second address, so surfacing that as a "parsed name" here would invite misuse. Call parseFromHeaderLoose alongside if you also need a name for display.

parseFromHeaderLoose#

Lenient parser for display-only call sites: inbox card "from" fields, log lines, debugging output.

import { parseFromHeaderLoose } from "@primitivedotdev/sdk/parser";

const parsed = parseFromHeaderLoose('"Alice" <alice@example.com>');
// { address: "alice@example.com", name: "Alice" }

Given a multi-address header, it returns the first address that passes validation rather than rejecting the header.


### Signature

```typescript
function parseFromHeaderLoose(
  header: string | null | undefined,
): ParsedAddress | null;

Result shape#

interface ParsedAddress {
  address: string; // lowercased
  name: string | null;
}

Returns null when the header is null/undefined, empty after trimming, exceeds the 998-byte limit, or contains no address that passes the same isEmail check used by parseFromHeader.

Behavior differences from parseFromHeader#

CaseparseFromHeaderparseFromHeaderLoose
Multiple addressesRejects (multiple_addresses)Returns the first parseable address
Group syntaxRejects (group_syntax)Flattens into member addresses and returns the first parseable one
No valid addressReturns a typed reasonReturns null
Display nameNever returnedReturned when present, otherwise null
Warning

Names returned by parseFromHeaderLoose can include addressparser's recovery output, trailing tokens or garbage preceding the address. Treat name as opaque text for display; sanitize before re-emitting it (e.g. into HTML or a re-sent email).

Choosing between them#

  • Authorization, allowlisting, or any trust decision: use parseFromHeader and branch on result.ok / result.reason.
  • Showing a sender's name in a UI or log: use parseFromHeaderLoose and treat name as untrusted display text.

Both parsers are pure functions with no network or I/O dependency; they operate only on the header string you pass in. For deciding whether an inbound email's sender domain can be trusted (not just whether the header parses), see Verifying Inbound Email Authenticity.

Next steps#

Was this page helpful?

© Primitive SDKs

Powered by Browzer