Email parser for Node.js and browser environments
npm install postal-mimepostal-mime is an email parsing library for Node.js, browsers (including Web Workers), and serverless environments (like Cloudflare Email Workers). It takes in a raw email message (RFC822 format) and outputs a structured object containing headers, recipients, attachments, and more.
> [!TIP]
> PostalMime is developed by the makers of EmailEngineβa self-hosted email gateway that provides a REST API for IMAP and SMTP servers and sends webhooks whenever something changes in registered accounts.
- Browser & Node.js compatible - Works in browsers, Web Workers, Node.js, and serverless environments
- TypeScript support - Fully typed with comprehensive type definitions
- Zero dependencies - No external dependencies
- RFC compliant - Follows RFC 2822/5322 email standards
- Handles complex MIME structures - Multipart messages, nested parts, attachments
- Security limits - Built-in protection against deeply nested messages and oversized headers
- Source
- Demo
- Installation
- Usage
- Browser
- Node.js
- Cloudflare Email Workers
- TypeScript Support
- API
- PostalMime.parse()
- Utility Functions
- addressParser()
- decodeWords()
- License
---
The source code is available on GitHub.
Try out a live demo using the example page.
Install the module from npm:
``bash`
npm install postal-mime
You can import the PostalMime class differently depending on your environment:
To use PostalMime in the browser (including Web Workers), import it from the src folder:
`js
import PostalMime from './node_modules/postal-mime/src/postal-mime.js';
const email = await PostalMime.parse(Subject: My awesome email π€
Content-Type: text/html; charset=utf-8
Hello world π΅βπ«
);console.log(email.subject); // "My awesome email π€"
`
TypeScript
`typescript
import PostalMime from './node_modules/postal-mime/src/postal-mime.js';
import type { Email } from 'postal-mime';const email: Email = await PostalMime.parse(
Subject: My awesome email π€Hello world π΅βπ«
);console.log(email.subject); // "My awesome email π€"
`$3
In Node.js (including serverless functions), import it directly from
postal-mime:`js
import PostalMime from 'postal-mime';
import util from 'node:util';const email = await PostalMime.parse(
Subject: My awesome email π€Hello world π΅βπ«
);// Use 'util.inspect' for pretty-printing
console.log(util.inspect(email, false, 22, true));
`
TypeScript
`typescript
import PostalMime from 'postal-mime';
import type { Email, PostalMimeOptions } from 'postal-mime';
import util from 'node:util';const options: PostalMimeOptions = {
attachmentEncoding: 'base64'
};
const email: Email = await PostalMime.parse(
Subject: My awesome email π€Hello world π΅βπ«
, options);// Use 'util.inspect' for pretty-printing
console.log(util.inspect(email, false, 22, true));
`$3
For projects using CommonJS (with
require()), postal-mime automatically provides the CommonJS build:`js
const PostalMime = require('postal-mime');
const { addressParser, decodeWords } = require('postal-mime');const email = await PostalMime.parse(
Subject: My awesome email π€Hello world π΅βπ«
);console.log(email.subject); // "My awesome email π€"
`> [!NOTE]
> The CommonJS build is automatically generated from the ESM source code during the build process. The package supports dual module format, so both
import and require() work seamlessly.$3
Use the
message.raw as the raw email data for parsing:`js
import PostalMime from 'postal-mime';export default {
async email(message, env, ctx) {
const email = await PostalMime.parse(message.raw);
console.log('Subject:', email.subject);
console.log('HTML:', email.html);
console.log('Text:', email.text);
}
};
`
TypeScript
`typescript
import PostalMime from 'postal-mime';
import type { Email } from 'postal-mime';export default {
async email(message: ForwardableEmailMessage, env: Env, ctx: ExecutionContext): Promise {
const email: Email = await PostalMime.parse(message.raw);
console.log('Subject:', email.subject);
console.log('HTML:', email.html);
console.log('Text:', email.text);
}
};
`---
TypeScript Support
PostalMime includes comprehensive TypeScript type definitions. All types are exported and can be imported from the main package:
`typescript
import PostalMime, { addressParser, decodeWords } from 'postal-mime';
import type {
Email,
Address,
Mailbox,
Header,
Attachment,
PostalMimeOptions,
AddressParserOptions,
RawEmail
} from 'postal-mime';
`> [!NOTE]
> PostalMime is written in JavaScript but provides comprehensive TypeScript type definitions. All types are validated through both compile-time type checking and runtime type validation tests to ensure accuracy.
$3
-
Email - The main parsed email object returned by PostalMime.parse()
- Address - Union type representing either a Mailbox or an address group
- Mailbox - Individual email address with name and address fields
- Header - Email header with key and value
- Attachment - Email attachment with metadata and content
- PostalMimeOptions - Configuration options for parsing
- AddressParserOptions - Configuration options for address parsing
- RawEmail - Union type for all accepted email input formats$3
TypeScript users can use type guards to narrow address types:
`typescript
import type { Address, Mailbox } from 'postal-mime';function isMailbox(addr: Address): addr is Mailbox {
return !('group' in addr) || addr.group === undefined;
}
// Usage
if (email.from && isMailbox(email.from)) {
console.log(email.from.address); // TypeScript knows this is a Mailbox
}
`---
API
$3
`js
PostalMime.parse(email, options) -> Promise
`- email: An RFC822 formatted email. This can be a
string, ArrayBuffer/Uint8Array, Blob, Buffer (Node.js), or a ReadableStream.
- options: Optional configuration object:
- rfc822Attachments (boolean, default: false): Treat message/rfc822 attachments without a Content-Disposition as attachments.
- forceRfc822Attachments (boolean, default: false): Treat _all_ message/rfc822 parts as attachments.
- attachmentEncoding (string, default: "arraybuffer"): Determines how attachment content is decoded in the parsed email:
- "base64"
- "utf8"
- "arraybuffer" (no decoding, returns ArrayBuffer)
- maxNestingDepth (number, default: 256): Maximum allowed MIME part nesting depth. Throws an error if exceeded.
- maxHeadersSize (number, default: 2097152): Maximum allowed total header size in bytes (default 2MB). Throws an error if exceeded.> [!IMPORTANT]
> The
maxNestingDepth and maxHeadersSize options provide built-in security against malicious emails with deeply nested MIME structures or oversized headers that could cause performance issues or memory exhaustion.Returns: A Promise that resolves to a structured
Email object with the following properties:- headers: An array of
Header objects, each containing:
- key: Lowercase header name (e.g., "dkim-signature").
- value: Unprocessed header value as a string.
- from, sender: Processed Address objects (can be a Mailbox or address group):
- name: Decoded display name, or an empty string if not set.
- address: Email address.
- group: Array of Mailbox objects (only for address groups).
- deliveredTo, returnPath: Single email addresses as strings.
- to, cc, bcc, replyTo: Arrays of Address objects (same structure as from).
- subject: Subject line of the email.
- messageId, inReplyTo, references: Values from their corresponding headers.
- date: The email's sending time in ISO 8601 format (or the original string if parsing fails).
- html: String containing the HTML content of the email.
- text: String containing the plain text content of the email.
- attachments: Array of Attachment objects:
- filename: String or null
- mimeType: String
- disposition: "attachment", "inline", or null
- related: Boolean (optional, true if it's an inline image)
- contentId: String (optional)
- content: ArrayBuffer or string, depending on attachmentEncoding
- encoding: "base64" or "utf8" (optional)
TypeScript Types
`typescript
import type {
Email,
Address,
Mailbox,
Header,
Attachment,
PostalMimeOptions,
RawEmail
} from 'postal-mime';// Main email parsing
const email: Email = await PostalMime.parse(rawEmail);
// With options
const options: PostalMimeOptions = {
attachmentEncoding: 'base64',
maxNestingDepth: 100
};
const email: Email = await PostalMime.parse(rawEmail, options);
// Working with addresses
if (email.from) {
// Address can be either a Mailbox or a Group
if ('group' in email.from && email.from.group) {
// It's a group
email.from.group.forEach((member: Mailbox) => {
console.log(member.address);
});
} else {
// It's a mailbox
const mailbox = email.from as Mailbox;
console.log(mailbox.address);
}
}
// Working with attachments
email.attachments.forEach((att: Attachment) => {
if (att.encoding === 'base64') {
// content is a string
const base64Content: string = att.content as string;
} else {
// content is ArrayBuffer (default)
const buffer: ArrayBuffer = att.content as ArrayBuffer;
}
});
`---
$3
#### addressParser()
`js
import { addressParser } from 'postal-mime';addressParser(addressStr, opts) -> Address[]
`- addressStr: A raw address header string.
- opts: Optional configuration:
- flatten (boolean, default:
false): If true, ignores address groups and returns a flat array of addresses.Returns: An array of
Address objects, which can be nested if address groups are present.Example:
`js
import { addressParser } from 'postal-mime';const addressStr = '=?utf-8?B?44Ko44Od44K544Kr44O844OJ?= ';
console.log(addressParser(addressStr));
// [ { name: 'γ¨γγΉγ«γΌγ', address: 'support@example.com' } ]
`
TypeScript
`typescript
import { addressParser } from 'postal-mime';
import type { Address, AddressParserOptions } from 'postal-mime';const addressStr = '=?utf-8?B?44Ko44Od44K544Kr44O844OJ?= ';
const addresses: Address[] = addressParser(addressStr);
// With options
const options: AddressParserOptions = { flatten: true };
const flatAddresses: Address[] = addressParser(addressStr, options);
`#### decodeWords()
`js
import { decodeWords } from 'postal-mime';decodeWords(encodedStr) -> string
`- encodedStr: A string that may contain MIME encoded-words.
Returns: A Unicode string with all encoded-words decoded.
Example:
`js
import { decodeWords } from 'postal-mime';const encodedStr = 'Hello, =?utf-8?B?44Ko44Od44K544Kr44O844OJ?=';
console.log(decodeWords(encodedStr));
// Hello, γ¨γγΉγ«γΌγ
`
TypeScript
`typescript
import { decodeWords } from 'postal-mime';const encodedStr = 'Hello, =?utf-8?B?44Ko44Od44K544Kr44O844OJ?=';
const decoded: string = decodeWords(encodedStr);
console.log(decoded); // Hello, γ¨γγΉγ«γΌγ
`---
License
© 2021β2026 Andris Reinman
postal-mime` is licensed under the MIT No Attribution license.