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 +7 -0
- package/README.md +229 -17
- package/package.json +5 -3
- package/postal-mime.d.ts +5 -8
- package/tsconfig.json +17 -0
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
|
-
>
|
|
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<
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
133
|
-
- `filename`
|
|
134
|
-
- `mimeType
|
|
135
|
-
- `disposition
|
|
136
|
-
- `related
|
|
137
|
-
- `contentId
|
|
138
|
-
- `content`
|
|
139
|
-
- `encoding`
|
|
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) ->
|
|
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
|
|
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.
|
|
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
|
-
|
|
54
|
+
export type AddressParserOptions = {
|
|
55
55
|
flatten?: boolean
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
export function addressParser (
|
|
59
59
|
str: string,
|
|
60
60
|
options?: AddressParserOptions
|
|
61
61
|
): Address[];
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
export function decodeWords (
|
|
64
64
|
str: string
|
|
65
65
|
): string;
|
|
66
66
|
|
|
67
|
-
|
|
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
|
-
|
|
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
|
+
}
|