postal-mime 2.4.7 β†’ 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.5.0](https://github.com/postalsys/postal-mime/compare/v2.4.7...v2.5.0) (2025-10-07)
4
+
5
+
6
+ ### Features
7
+
8
+ * add comprehensive type validation and TypeScript support ([81a6467](https://github.com/postalsys/postal-mime/commit/81a6467b6c5379c502bac9ee7023e2c2dee976cb))
9
+
3
10
  ## [2.4.7](https://github.com/postalsys/postal-mime/compare/v2.4.6...v2.4.7) (2025-10-07)
4
11
 
5
12
 
package/README.md CHANGED
@@ -2,9 +2,18 @@
2
2
 
3
3
  **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.
4
4
 
5
- > **Tip**
5
+ > [!TIP]
6
6
  > PostalMime is developed by the makers of [EmailEngine](https://emailengine.app/?utm_source=github&utm_campaign=imapflow&utm_medium=readme-link)β€”a self-hosted email gateway that provides a REST API for IMAP and SMTP servers and sends webhooks whenever something changes in registered accounts.
7
7
 
8
+ ## Features
9
+
10
+ - **Browser & Node.js compatible** - Works in browsers, Web Workers, Node.js, and serverless environments
11
+ - **TypeScript support** - Fully typed with comprehensive type definitions
12
+ - **Zero dependencies** - No external dependencies
13
+ - **RFC compliant** - Follows RFC 2822/5322 email standards
14
+ - **Handles complex MIME structures** - Multipart messages, nested parts, attachments
15
+ - **Security limits** - Built-in protection against deeply nested messages and oversized headers
16
+
8
17
  ## Table of Contents
9
18
 
10
19
  - [Source](#source)
@@ -14,6 +23,7 @@
14
23
  - [Browser](#browser)
15
24
  - [Node.js](#nodejs)
16
25
  - [Cloudflare Email Workers](#cloudflare-email-workers)
26
+ - [TypeScript Support](#typescript-support)
17
27
  - [API](#api)
18
28
  - [PostalMime.parse()](#postalmimeparse)
19
29
  - [Utility Functions](#utility-functions)
@@ -58,6 +68,23 @@ Content-Type: text/html; charset=utf-8
58
68
  console.log(email.subject); // "My awesome email πŸ€“"
59
69
  ```
60
70
 
71
+ <details>
72
+ <summary><strong>TypeScript</strong></summary>
73
+
74
+ ```typescript
75
+ import PostalMime from './node_modules/postal-mime/src/postal-mime.js';
76
+ import type { Email } from 'postal-mime';
77
+
78
+ const email: Email = await PostalMime.parse(`Subject: My awesome email πŸ€“
79
+ Content-Type: text/html; charset=utf-8
80
+
81
+ <p>Hello world πŸ˜΅β€πŸ’«</p>`);
82
+
83
+ console.log(email.subject); // "My awesome email πŸ€“"
84
+ ```
85
+
86
+ </details>
87
+
61
88
  ### Node.js
62
89
 
63
90
  In Node.js (including serverless functions), import it directly from `postal-mime`:
@@ -75,6 +102,29 @@ Content-Type: text/html; charset=utf-8
75
102
  console.log(util.inspect(email, false, 22, true));
76
103
  ```
77
104
 
105
+ <details>
106
+ <summary><strong>TypeScript</strong></summary>
107
+
108
+ ```typescript
109
+ import PostalMime from 'postal-mime';
110
+ import type { Email, PostalMimeOptions } from 'postal-mime';
111
+ import util from 'node:util';
112
+
113
+ const options: PostalMimeOptions = {
114
+ attachmentEncoding: 'base64'
115
+ };
116
+
117
+ const email: Email = await PostalMime.parse(`Subject: My awesome email πŸ€“
118
+ Content-Type: text/html; charset=utf-8
119
+
120
+ <p>Hello world πŸ˜΅β€πŸ’«</p>`, options);
121
+
122
+ // Use 'util.inspect' for pretty-printing
123
+ console.log(util.inspect(email, false, 22, true));
124
+ ```
125
+
126
+ </details>
127
+
78
128
  ### Cloudflare Email Workers
79
129
 
80
130
  Use the `message.raw` as the raw email data for parsing:
@@ -93,6 +143,77 @@ export default {
93
143
  };
94
144
  ```
95
145
 
146
+ <details>
147
+ <summary><strong>TypeScript</strong></summary>
148
+
149
+ ```typescript
150
+ import PostalMime from 'postal-mime';
151
+ import type { Email } from 'postal-mime';
152
+
153
+ export default {
154
+ async email(message: ForwardableEmailMessage, env: Env, ctx: ExecutionContext): Promise<void> {
155
+ const email: Email = await PostalMime.parse(message.raw);
156
+
157
+ console.log('Subject:', email.subject);
158
+ console.log('HTML:', email.html);
159
+ console.log('Text:', email.text);
160
+ }
161
+ };
162
+ ```
163
+
164
+ </details>
165
+
166
+ ---
167
+
168
+ ## TypeScript Support
169
+
170
+ PostalMime includes comprehensive TypeScript type definitions. All types are exported and can be imported from the main package:
171
+
172
+ ```typescript
173
+ import PostalMime, { addressParser, decodeWords } from 'postal-mime';
174
+ import type {
175
+ Email,
176
+ Address,
177
+ Mailbox,
178
+ Header,
179
+ Attachment,
180
+ PostalMimeOptions,
181
+ AddressParserOptions,
182
+ RawEmail
183
+ } from 'postal-mime';
184
+ ```
185
+
186
+ > [!NOTE]
187
+ > 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.
188
+
189
+ ### Available Types
190
+
191
+ - **`Email`** - The main parsed email object returned by `PostalMime.parse()`
192
+ - **`Address`** - Union type representing either a `Mailbox` or an address group
193
+ - **`Mailbox`** - Individual email address with name and address fields
194
+ - **`Header`** - Email header with key and value
195
+ - **`Attachment`** - Email attachment with metadata and content
196
+ - **`PostalMimeOptions`** - Configuration options for parsing
197
+ - **`AddressParserOptions`** - Configuration options for address parsing
198
+ - **`RawEmail`** - Union type for all accepted email input formats
199
+
200
+ ### Type Narrowing
201
+
202
+ TypeScript users can use type guards to narrow address types:
203
+
204
+ ```typescript
205
+ import type { Address, Mailbox } from 'postal-mime';
206
+
207
+ function isMailbox(addr: Address): addr is Mailbox {
208
+ return !('group' in addr) || addr.group === undefined;
209
+ }
210
+
211
+ // Usage
212
+ if (email.from && isMailbox(email.from)) {
213
+ console.log(email.from.address); // TypeScript knows this is a Mailbox
214
+ }
215
+ ```
216
+
96
217
  ---
97
218
 
98
219
  ## API
@@ -100,7 +221,7 @@ export default {
100
221
  ### PostalMime.parse()
101
222
 
102
223
  ```js
103
- PostalMime.parse(email, options) -> Promise<ParsedEmail>
224
+ PostalMime.parse(email, options) -> Promise<Email>
104
225
  ```
105
226
 
106
227
  - **email**: An RFC822 formatted email. This can be a `string`, `ArrayBuffer/Uint8Array`, `Blob` (browser only), `Buffer` (Node.js), or a [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
@@ -114,29 +235,86 @@ PostalMime.parse(email, options) -> Promise<ParsedEmail>
114
235
  - **maxNestingDepth** (number, default: `256`): Maximum allowed MIME part nesting depth. Throws an error if exceeded.
115
236
  - **maxHeadersSize** (number, default: `2097152`): Maximum allowed total header size in bytes (default 2MB). Throws an error if exceeded.
116
237
 
117
- **Returns**: A Promise that resolves to a structured object with the following properties:
238
+ > [!IMPORTANT]
239
+ > 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.
240
+
241
+ **Returns**: A Promise that resolves to a structured `Email` object with the following properties:
118
242
 
119
- - **headers**: An array of header objects, each containing:
243
+ - **headers**: An array of `Header` objects, each containing:
120
244
  - `key`: Lowercase header name (e.g., `"dkim-signature"`).
121
245
  - `value`: Unprocessed header value as a string.
122
- - **from**, **sender**: Processed address objects:
246
+ - **from**, **sender**: Processed `Address` objects (can be a `Mailbox` or address group):
123
247
  - `name`: Decoded display name, or an empty string if not set.
124
248
  - `address`: Email address.
249
+ - `group`: Array of `Mailbox` objects (only for address groups).
125
250
  - **deliveredTo**, **returnPath**: Single email addresses as strings.
126
- - **to**, **cc**, **bcc**, **replyTo**: Arrays of processed address objects (same structure as `from`).
251
+ - **to**, **cc**, **bcc**, **replyTo**: Arrays of `Address` objects (same structure as `from`).
127
252
  - **subject**: Subject line of the email.
128
253
  - **messageId**, **inReplyTo**, **references**: Values from their corresponding headers.
129
- - **date**: The email’s sending time in ISO 8601 format (or the original string if parsing fails).
254
+ - **date**: The email's sending time in ISO 8601 format (or the original string if parsing fails).
130
255
  - **html**: String containing the HTML content of the email.
131
256
  - **text**: String containing the plain text content of the email.
132
- - **attachments**: Array of attachment objects:
133
- - `filename`
134
- - `mimeType`
135
- - `disposition` (e.g., `"attachment"`, `"inline"`, or `null`)
136
- - `related` (boolean, `true` if it’s an inline image)
137
- - `contentId`
138
- - `content` (array buffer or string, depending on `attachmentEncoding`)
139
- - `encoding` (e.g., `"base64"`)
257
+ - **attachments**: Array of `Attachment` objects:
258
+ - `filename`: String or `null`
259
+ - `mimeType`: String
260
+ - `disposition`: `"attachment"`, `"inline"`, or `null`
261
+ - `related`: Boolean (optional, `true` if it's an inline image)
262
+ - `contentId`: String (optional)
263
+ - `content`: `ArrayBuffer` or string, depending on `attachmentEncoding`
264
+ - `encoding`: `"base64"` or `"utf8"` (optional)
265
+
266
+ <details>
267
+ <summary><strong>TypeScript Types</strong></summary>
268
+
269
+ ```typescript
270
+ import type {
271
+ Email,
272
+ Address,
273
+ Mailbox,
274
+ Header,
275
+ Attachment,
276
+ PostalMimeOptions,
277
+ RawEmail
278
+ } from 'postal-mime';
279
+
280
+ // Main email parsing
281
+ const email: Email = await PostalMime.parse(rawEmail);
282
+
283
+ // With options
284
+ const options: PostalMimeOptions = {
285
+ attachmentEncoding: 'base64',
286
+ maxNestingDepth: 100
287
+ };
288
+ const email: Email = await PostalMime.parse(rawEmail, options);
289
+
290
+ // Working with addresses
291
+ if (email.from) {
292
+ // Address can be either a Mailbox or a Group
293
+ if ('group' in email.from && email.from.group) {
294
+ // It's a group
295
+ email.from.group.forEach((member: Mailbox) => {
296
+ console.log(member.address);
297
+ });
298
+ } else {
299
+ // It's a mailbox
300
+ const mailbox = email.from as Mailbox;
301
+ console.log(mailbox.address);
302
+ }
303
+ }
304
+
305
+ // Working with attachments
306
+ email.attachments.forEach((att: Attachment) => {
307
+ if (att.encoding === 'base64') {
308
+ // content is a string
309
+ const base64Content: string = att.content as string;
310
+ } else {
311
+ // content is ArrayBuffer (default)
312
+ const buffer: ArrayBuffer = att.content as ArrayBuffer;
313
+ }
314
+ });
315
+ ```
316
+
317
+ </details>
140
318
 
141
319
  ---
142
320
 
@@ -147,23 +325,42 @@ PostalMime.parse(email, options) -> Promise<ParsedEmail>
147
325
  ```js
148
326
  import { addressParser } from 'postal-mime';
149
327
 
150
- addressParser(addressStr, opts) -> Array
328
+ addressParser(addressStr, opts) -> Address[]
151
329
  ```
152
330
 
153
331
  - **addressStr**: A raw address header string.
154
332
  - **opts**: Optional configuration:
155
333
  - **flatten** (boolean, default: `false`): If `true`, ignores address groups and returns a flat array of addresses.
156
334
 
157
- **Returns**: An array of address objects, which can be nested if address groups are present.
335
+ **Returns**: An array of `Address` objects, which can be nested if address groups are present.
158
336
 
159
337
  **Example**:
160
338
 
161
339
  ```js
340
+ import { addressParser } from 'postal-mime';
341
+
162
342
  const addressStr = '=?utf-8?B?44Ko44Od44K544Kr44O844OJ?= <support@example.com>';
163
343
  console.log(addressParser(addressStr));
164
344
  // [ { name: 'エポスカード', address: 'support@example.com' } ]
165
345
  ```
166
346
 
347
+ <details>
348
+ <summary><strong>TypeScript</strong></summary>
349
+
350
+ ```typescript
351
+ import { addressParser } from 'postal-mime';
352
+ import type { Address, AddressParserOptions } from 'postal-mime';
353
+
354
+ const addressStr = '=?utf-8?B?44Ko44Od44K544Kr44O844OJ?= <support@example.com>';
355
+ const addresses: Address[] = addressParser(addressStr);
356
+
357
+ // With options
358
+ const options: AddressParserOptions = { flatten: true };
359
+ const flatAddresses: Address[] = addressParser(addressStr, options);
360
+ ```
361
+
362
+ </details>
363
+
167
364
  #### decodeWords()
168
365
 
169
366
  ```js
@@ -179,11 +376,26 @@ decodeWords(encodedStr) -> string
179
376
  **Example**:
180
377
 
181
378
  ```js
379
+ import { decodeWords } from 'postal-mime';
380
+
182
381
  const encodedStr = 'Hello, =?utf-8?B?44Ko44Od44K544Kr44O844OJ?=';
183
382
  console.log(decodeWords(encodedStr));
184
383
  // Hello, エポスカード
185
384
  ```
186
385
 
386
+ <details>
387
+ <summary><strong>TypeScript</strong></summary>
388
+
389
+ ```typescript
390
+ import { decodeWords } from 'postal-mime';
391
+
392
+ const encodedStr = 'Hello, =?utf-8?B?44Ko44Od44K544Kr44O844OJ?=';
393
+ const decoded: string = decodeWords(encodedStr);
394
+ console.log(decoded); // Hello, エポスカード
395
+ ```
396
+
397
+ </details>
398
+
187
399
  ---
188
400
 
189
401
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postal-mime",
3
- "version": "2.4.7",
3
+ "version": "2.5.0",
4
4
  "description": "Email parser for browser environments",
5
5
  "main": "./src/postal-mime.js",
6
6
  "exports": {
@@ -11,8 +11,9 @@
11
11
  "type": "module",
12
12
  "types": "postal-mime.d.ts",
13
13
  "scripts": {
14
- "test": "npm run lint && node --test",
14
+ "test": "npm run lint && npm run type-check && node --test",
15
15
  "lint": "eslint",
16
+ "type-check": "tsc --noEmit",
16
17
  "update": "rm -rf node_modules package-lock.json && ncu -u && npm install",
17
18
  "format": "prettier --write \"**/*.{js,mjs,json}\" --ignore-path .prettierignore",
18
19
  "format:check": "prettier --check \"**/*.{js,mjs,json}\" --ignore-path .prettierignore"
@@ -37,6 +38,7 @@
37
38
  "eslint": "8.57.0",
38
39
  "eslint-cli": "1.1.1",
39
40
  "iframe-resizer": "4.3.6",
40
- "prettier": "^3.6.2"
41
+ "prettier": "^3.6.2",
42
+ "typescript": "^5.9.3"
41
43
  }
42
44
  }
package/postal-mime.d.ts CHANGED
@@ -51,20 +51,20 @@ export type Email = {
51
51
  attachments: Attachment[];
52
52
  };
53
53
 
54
- declare type AddressParserOptions = {
54
+ export type AddressParserOptions = {
55
55
  flatten?: boolean
56
56
  }
57
57
 
58
- declare function addressParser (
58
+ export function addressParser (
59
59
  str: string,
60
60
  options?: AddressParserOptions
61
61
  ): Address[];
62
62
 
63
- declare function decodeWords (
63
+ export function decodeWords (
64
64
  str: string
65
65
  ): string;
66
66
 
67
- declare type PostalMimeOptions = {
67
+ export type PostalMimeOptions = {
68
68
  rfc822Attachments?: boolean,
69
69
  forceRfc822Attachments?: boolean,
70
70
  attachmentEncoding?: "base64" | "utf8" | "arraybuffer",
@@ -72,7 +72,7 @@ declare type PostalMimeOptions = {
72
72
  maxHeadersSize?: number
73
73
  }
74
74
 
75
- declare class PostalMime {
75
+ export default class PostalMime {
76
76
  constructor(options?: PostalMimeOptions);
77
77
  static parse(
78
78
  email: RawEmail,
@@ -80,6 +80,3 @@ declare class PostalMime {
80
80
  ): Promise<Email>;
81
81
  parse(email: RawEmail): Promise<Email>;
82
82
  }
83
-
84
- export { addressParser, decodeWords };
85
- export default PostalMime;
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "lib": ["ES2022", "DOM"],
6
+ "moduleResolution": "bundler",
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "skipLibCheck": true,
10
+ "esModuleInterop": true,
11
+ "allowSyntheticDefaultImports": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "types": ["node"]
14
+ },
15
+ "include": ["test/type-check.test.ts"],
16
+ "exclude": ["node_modules"]
17
+ }