{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/node-sdk-parsing-email","markdown_url":"https://test.abhinandan.one/node-sdk-parsing-email.md","article":{"id":"229f07e9-8569-40e6-9c06-45c1ca8d2470","article_slug":"node-sdk-parsing-email","parent_article_slug":null,"parent_article_title":null,"kind":"guide","published_at":"2026-08-11T18:54:52.394319+00:00","keywords":["parseFromHeader","parseFromHeaderLoose","@primitivedotdev/sdk/parser","parse.eml file","ValidatedAddress","ParsedAddress"],"meta_description":"Parse raw MIME/.eml bytes into bodies, headers, and attachments with the Node SDK, then extract sender addresses strictly with parseFromHeader.","og_image_url":null,"source_file_paths":["sdk-node/src/parser/address-parser.ts"],"recording_id":null,"replayable":false,"task_name":"Parsing Raw Email (.eml)","category":"Node.js SDK","summary":null,"description":"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.","content_kind":"repo_page","content_markdown":"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](node-sdk-download-tokens), or a From header you need to validate for an authorization decision. Most webhook handlers never need this, [`primitive.receive(...)`](node-sdk-receiving-email) already gives you a normalized `ReceivedEmail` with `sender`, `text`, and `thread` extracted for you.\n\nReach for the parser module when:\n\n- You downloaded the raw email via a download-token URL or `email.raw` and need to walk its MIME structure yourself.\n- You have a From/Sender/Reply-To header string and need a strict, security-safe parse before gating an action on it.\n- You're rendering inbound HTML in a browser and need it sanitized first.\n\n<Note>\n\n`@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.\n\n</Note>\n\n## Extract a validated address from a From header\n\n`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`:\n\n```typescript\nimport { parseFromHeader } from \"@primitivedotdev/sdk/parser\";\n\nconst result = parseFromHeader('\"Alice\" <alice@example.com>');\n\nif (result.ok) {\n  console.log(result.value.address); // \"alice@example.com\"\n} else {\n  console.log(result.reason); // e.g. \"multiple_addresses\"\n}\n```\n\n`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)](node-sdk-address-parsing).\n\n<Warning>\n\nDo 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.\n\n</Warning>\n\n<Tip>\n\nFor deciding whether an inbound email is authentic (SPF/DKIM/DMARC), use [`isTrustedSender`](node-sdk-email-authenticity) 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.\n\n</Tip>\n\n## Parse a header for display purposes\n\nWhen you're rendering a \"from\" field in a UI or log line, not gating access, use the lenient parser instead:\n\n```typescript\nimport { parseFromHeaderLoose } from \"@primitivedotdev/sdk/parser\";\n\nconst parsed = parseFromHeaderLoose('Alice Smith <alice@example.com>, bob@example.com');\n\nif (parsed) {\n  console.log(parsed.address); // \"alice@example.com\" (first address)\n  console.log(parsed.name);    // \"Alice Smith\"\n}\n```\n\n`parseFromHeaderLoose` returns `ParsedAddress | null`, taking the first parseable address (flattening group syntax) and including the display name; see [Raw MIME Address Parsing (parser/address)](node-sdk-address-parsing) for the full behavior comparison.\n\n<Warning>\n\nNever 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.\n\n</Warning>\n\n## Choosing between the two parsers\n\n<Steps>\n\n<Step title=\"Identify what the parsed address controls\">\n\nIf 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.\n\n</Step>\n\n<Step title=\"Handle the failure path explicitly\">\n\n`parseFromHeader` never throws, it returns `{ ok: false, reason }`. Branch on `reason` to give a specific error rather than a generic \"invalid sender\" message:\n\n```typescript\nif (!result.ok) {\n  switch (result.reason) {\n    case \"multiple_addresses\":\n      // reject: ambiguous sender identity\n      break;\n    case \"too_long\":\n      // reject: probable header injection or corrupt feed\n      break;\n    default:\n      // reject: malformed or empty\n      break;\n  }\n}\n```\n\n</Step>\n\n<Step title=\"Lowercase-compare, don't case-compare\">\n\nBoth parsers already lowercase the returned `address`. Compare against a lowercased allowlist value, not the original-case header string.\n\n</Step>\n\n</Steps>\n\n## Working with the raw email body and attachments\n\nFor 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](node-sdk-receiving-email). Once you have those attachments, [Bundling Attachments](node-sdk-attachment-bundling) covers packaging them into a single archive. The helpers documented on this page are the address parsers from the same `parser` subpath.\n\n## Sanitizing inbound HTML for rendering\n\nTreat any inbound HTML body as attacker-controlled input and run it through a sanitizer before injecting it into the DOM. See [Receiving Inbound Email](node-sdk-receiving-email) for the fields available on `ReceivedEmail`, then handle the HTML the same way you would any other untrusted markup.\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"Receiving Inbound Email\" href=\"node-sdk-receiving-email\">\n\nGet the normalized ReceivedEmail shape without touching raw MIME directly.\n\n</Card>\n\n<Card title=\"Verifying Inbound Email Authenticity\" href=\"node-sdk-email-authenticity\">\n\nAnchor authorization decisions to SPF/DKIM/DMARC results, not header text alone.\n\n</Card>\n\n<Card title=\"Bundling Attachments\" href=\"node-sdk-attachment-bundling\">\n\nPackage extracted attachments into a content-addressed archive.\n\n</Card>\n\n<Card title=\"Node.js SDK Errors\" href=\"node-sdk-errors\">\n\nLook up the error types thrown by webhook and parsing helpers.\n\n</Card>\n\n</CardGroup>","canonical_base_url":"https://test.abhinandan.one","seo_indexing_enabled":true,"last_modified":"2026-08-21T18:22:43.359885+00:00","video_url":null,"voiceover_url":null,"tools_used":[],"demonstrated_by":[],"steps":[],"related_links":[],"intro":null,"prerequisites":[],"verification":[],"troubleshooting":[],"suggest_edit_url":"https://github.com/abhi-browzer/primitive-sdks/edit/main/sdk-node/src/parser/address-parser.ts","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Parsing+Raw+Email+%28.eml%29&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fnode-sdk-parsing-email","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}