postal-mime is an email parsing library that runs in browser environments (including Web Workers) and serverless functions (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
The source code is available on GitHub.
Try out a live demo using the example page.
Install the module from npm:
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:
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
<p>Hello world 😵💫</p>`);
console.log(email.subject); // "My awesome email 🤓"
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 🤓
Content-Type: text/html; charset=utf-8
<p>Hello world 😵💫</p>`);
console.log(email.subject); // "My awesome email 🤓"
In Node.js (including serverless functions), import it directly from postal-mime
:
import PostalMime from 'postal-mime';
import util from 'node:util';
const email = await PostalMime.parse(`Subject: My awesome email 🤓
Content-Type: text/html; charset=utf-8
<p>Hello world 😵💫</p>`);
// Use 'util.inspect' for pretty-printing
console.log(util.inspect(email, false, 22, true));
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 🤓
Content-Type: text/html; charset=utf-8
<p>Hello world 😵💫</p>`, options);
// Use 'util.inspect' for pretty-printing
console.log(util.inspect(email, false, 22, true));
Use the message.raw
as the raw email data for parsing:
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
import PostalMime from 'postal-mime';
import type { Email } from 'postal-mime';
export default {
async email(message: ForwardableEmailMessage, env: Env, ctx: ExecutionContext): Promise<void> {
const email: Email = await PostalMime.parse(message.raw);
console.log('Subject:', email.subject);
console.log('HTML:', email.html);
console.log('Text:', email.text);
}
};
PostalMime includes comprehensive TypeScript type definitions. All types are exported and can be imported from the main package:
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.
Email
- The main parsed email object returned byPostalMime.parse()
Address
- Union type representing either aMailbox
or an address groupMailbox
- Individual email address with name and address fieldsHeader
- Email header with key and valueAttachment
- Email attachment with metadata and contentPostalMimeOptions
- Configuration options for parsingAddressParserOptions
- Configuration options for address parsingRawEmail
- Union type for all accepted email input formats
TypeScript users can use type guards to narrow address types:
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
}
PostalMime.parse(email, options) -> Promise<Email>
- email: An RFC822 formatted email. This can be a
string
,ArrayBuffer/Uint8Array
,Blob
(browser only),Buffer
(Node.js), or a ReadableStream. - options: Optional configuration object:
- rfc822Attachments (boolean, default:
false
): Treatmessage/rfc822
attachments without a Content-Disposition as attachments. - forceRfc822Attachments (boolean, default:
false
): Treat allmessage/rfc822
parts as attachments. - attachmentEncoding (string, default:
"arraybuffer"
): Determines how attachment content is decoded in the parsed email:"base64"
"utf8"
"arraybuffer"
(no decoding, returnsArrayBuffer
)
- 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.
- rfc822Attachments (boolean, default:
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 aMailbox
or address group):name
: Decoded display name, or an empty string if not set.address
: Email address.group
: Array ofMailbox
objects (only for address groups).
- deliveredTo, returnPath: Single email addresses as strings.
- to, cc, bcc, replyTo: Arrays of
Address
objects (same structure asfrom
). - 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 ornull
mimeType
: Stringdisposition
:"attachment"
,"inline"
, ornull
related
: Boolean (optional,true
if it's an inline image)contentId
: String (optional)content
:ArrayBuffer
or string, depending onattachmentEncoding
encoding
:"base64"
or"utf8"
(optional)
TypeScript Types
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;
}
});
import { addressParser } from 'postal-mime';
addressParser(addressStr, opts) -> Address[]
- addressStr: A raw address header string.
- opts: Optional configuration:
- flatten (boolean, default:
false
): Iftrue
, ignores address groups and returns a flat array of addresses.
- flatten (boolean, default:
Returns: An array of Address
objects, which can be nested if address groups are present.
Example:
import { addressParser } from 'postal-mime';
const addressStr = '=?utf-8?B?44Ko44Od44K544Kr44O844OJ?= <[email protected]>';
console.log(addressParser(addressStr));
// [ { name: 'エポスカード', address: 'support@example.com' } ]
TypeScript
import { addressParser } from 'postal-mime';
import type { Address, AddressParserOptions } from 'postal-mime';
const addressStr = '=?utf-8?B?44Ko44Od44K544Kr44O844OJ?= <[email protected]>';
const addresses: Address[] = addressParser(addressStr);
// With options
const options: AddressParserOptions = { flatten: true };
const flatAddresses: Address[] = addressParser(addressStr, options);
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:
import { decodeWords } from 'postal-mime';
const encodedStr = 'Hello, =?utf-8?B?44Ko44Od44K544Kr44O844OJ?=';
console.log(decodeWords(encodedStr));
// Hello, エポスカード
TypeScript
import { decodeWords } from 'postal-mime';
const encodedStr = 'Hello, =?utf-8?B?44Ko44Od44K544Kr44O844OJ?=';
const decoded: string = decodeWords(encodedStr);
console.log(decoded); // Hello, エポスカード
© 2021–2025 Andris Reinman
postal-mime
is licensed under the MIT No Attribution license.