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

Parsing Raw Email (.eml)

Parse raw MIME bytes into structured bodies, headers, and attachments with the Node SDK's parser module, then extract addresses safely and sanitize HTML before rendering it.

Use the Node SDK's parser subpath when you need to work with raw MIME bytes directly: a .eml file on disk, a full raw email downloaded from Signed Download Tokens, or a From header you need to validate for an authorization decision. Most webhook handlers never need this, primitive.receive(...) already gives you a normalized ReceivedEmail with sender, text, and thread extracted for you.

Reach for the parser module when:

  • You downloaded the raw email via a download-token URL or email.raw and need to walk its MIME structure yourself.
  • You have a From/Sender/Reply-To header string and need a strict, security-safe parse before gating an action on it.
  • You're rendering inbound HTML in a browser and need it sanitized first.
Note

@primitivedotdev/sdk/parser is Node-only (it depends on nodemailer's address parser and validator). It is not exported from the Workers-safe /api or /webhook subpaths. Don't import it into a Primitive Function handler.

Extract a validated address from a From header#

parseFromHeader is the strict parser for security-bearing contexts, allowlist gates, permission grants, anything that decides "did this really come from example.com." Import it from @primitivedotdev/sdk/parser:

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); // e.g. "multiple_addresses"
}

parseFromHeader returns a typed ParseFromHeaderResult whose ok: false branch carries a stable ParseFromHeaderFailureReason; the full result shape, rejection reasons, and normalization rules are documented in Raw MIME Address Parsing (parser/address).

Warning

Do not build your own allowlist check by regexing the raw From header. A header like From: "trusted@example.com" <x@evil.com> puts an allowlisted address in the display name while the real address is evil.com. parseFromHeader avoids this because it never treats the display name as the address.

Tip

For deciding whether an inbound email is authentic (SPF/DKIM/DMARC), use isTrustedSender instead, it's the purpose-built domain-anchored trust check. parseFromHeader is for extracting and validating the shape of an address string you already have; it does not check authentication.

Parse a header for display purposes#

When you're rendering a "from" field in a UI or log line, not gating access, use the lenient parser instead:

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

const parsed = parseFromHeaderLoose('Alice Smith <alice@example.com>, bob@example.com');

if (parsed) {
  console.log(parsed.address); // "alice@example.com" (first address)
  console.log(parsed.name);    // "Alice Smith"
}

parseFromHeaderLoose returns ParsedAddress | null, taking the first parseable address (flattening group syntax) and including the display name; see Raw MIME Address Parsing (parser/address) for the full behavior comparison.

Warning

Never use parseFromHeaderLoose for permission gates or any decision that grants access. Its name field can include address-parser recovery artifacts (trailing tokens, garbage before the address), treat it as opaque display text only. Use parseFromHeader for anything security-bearing.

Choosing between the two parsers#

  1. 1

    Identify what the parsed address controls#

    If the result gates an action (reply-from validation, sender allowlisting, authorization of any kind), you need parseFromHeader. If the result only appears in a UI, log, or notification, parseFromHeaderLoose is fine.

  2. 2

    Handle the failure path explicitly#

    parseFromHeader never throws, it returns { ok: false, reason }. Branch on reason to give a specific error rather than a generic "invalid sender" message:

    if (!result.ok) {
      switch (result.reason) {
        case "multiple_addresses":
          // reject: ambiguous sender identity
          break;
        case "too_long":
          // reject: probable header injection or corrupt feed
          break;
        default:
          // reject: malformed or empty
          break;
      }
    }
    
  3. 3

    Lowercase-compare, don't case-compare#

    Both parsers already lowercase the returned address. Compare against a lowercased allowlist value, not the original-case header string.

Working with the raw email body and attachments#

For most inbound mail you never need to walk the MIME tree yourself: primitive.receive(...) already returns a normalized ReceivedEmail with text, thread, and the attachment list, as described in Receiving Inbound Email. Once you have those attachments, Bundling Attachments covers packaging them into a single archive. The helpers documented on this page are the address parsers from the same parser subpath.

Sanitizing inbound HTML for rendering#

Treat any inbound HTML body as attacker-controlled input and run it through a sanitizer before injecting it into the DOM. See Receiving Inbound Email for the fields available on ReceivedEmail, then handle the HTML the same way you would any other untrusted markup.

Next steps#

Was this page helpful?

© Primitive SDKs

Powered by Browzer