imapflow 1.2.9 → 1.2.10
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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +7 -0
- package/README.md +36 -58
- package/eslint.config.js +18 -16
- package/lib/imap-flow.js +11 -4
- package/package.json +5 -10
- package/test/connection-edge-cases-test.js +105 -0
- package/.babelrc +0 -6
- package/.eslintrc +0 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.10](https://github.com/postalsys/imapflow/compare/v1.2.9...v1.2.10) (2026-02-19)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* add null guards in compress() to prevent crash on TLS teardown ([84cd3ff](https://github.com/postalsys/imapflow/commit/84cd3ff8815d8ba37a37c1e7a09c244e80b61948))
|
|
9
|
+
|
|
3
10
|
## [1.2.9](https://github.com/postalsys/imapflow/compare/v1.2.8...v1.2.9) (2026-02-06)
|
|
4
11
|
|
|
5
12
|
|
package/README.md
CHANGED
|
@@ -1,109 +1,87 @@
|
|
|
1
1
|
# ImapFlow
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Modern and easy-to-use IMAP client library for Node.js.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
The focus for ImapFlow is to provide easy to use API over IMAP. Using ImapFlow does not expect knowledge about specific IMAP details. A general understanding is good enough.
|
|
9
|
-
|
|
10
|
-
IMAP extensions are handled in the background, so, for example, you can always request `labels` value from a `fetch()` call, but if the IMAP server does not support `X-GM-EXT-1` extension, then `labels` value is not included in the response.
|
|
5
|
+
[](https://www.npmjs.com/package/imapflow)
|
|
6
|
+
[](https://github.com/postalsys/imapflow/blob/master/LICENSE)
|
|
11
7
|
|
|
12
|
-
|
|
8
|
+
ImapFlow provides a clean, promise-based API for working with IMAP, so you don't need in-depth knowledge of the protocol. IMAP extensions are detected and handled automatically. You write the same code regardless of server capabilities, and ImapFlow adapts behind the scenes.
|
|
13
9
|
|
|
14
|
-
|
|
10
|
+
## Features
|
|
15
11
|
|
|
16
|
-
|
|
12
|
+
- **Async/await API** - all methods return Promises
|
|
13
|
+
- **Automatic IMAP extension handling** - CONDSTORE, QRESYNC, IDLE, COMPRESS, and [more](https://imapflow.com/docs/)
|
|
14
|
+
- **Message streaming** - async iterators for efficient processing
|
|
15
|
+
- **Mailbox locking** - built-in locking mechanism for safe concurrent access
|
|
16
|
+
- **TypeScript support** - type definitions included
|
|
17
|
+
- **Proxy support** - SOCKS and HTTP CONNECT proxies
|
|
18
|
+
- **Gmail support** - labels, raw search via X-GM-EXT-1
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
## Installation
|
|
19
21
|
|
|
20
|
-
```
|
|
22
|
+
```bash
|
|
21
23
|
npm install imapflow
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
## Quick Example
|
|
25
27
|
|
|
26
28
|
```js
|
|
27
29
|
const { ImapFlow } = require('imapflow');
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### Promises
|
|
31
30
|
|
|
32
|
-
All ImapFlow methods use Promises, so you need to wait using `await` or wait for the `then()` method to fire until you get the response.
|
|
33
|
-
|
|
34
|
-
```js
|
|
35
|
-
const { ImapFlow } = require('imapflow');
|
|
36
31
|
const client = new ImapFlow({
|
|
37
|
-
host: '
|
|
32
|
+
host: 'imap.example.com',
|
|
38
33
|
port: 993,
|
|
39
34
|
secure: true,
|
|
40
35
|
auth: {
|
|
41
|
-
user: '
|
|
42
|
-
pass: '
|
|
36
|
+
user: 'user@example.com',
|
|
37
|
+
pass: 'password'
|
|
43
38
|
}
|
|
44
39
|
});
|
|
45
40
|
|
|
46
41
|
const main = async () => {
|
|
47
|
-
// Wait until client connects and authorizes
|
|
48
42
|
await client.connect();
|
|
49
43
|
|
|
50
|
-
// Select and lock a mailbox. Throws if mailbox does not exist
|
|
51
44
|
let lock = await client.getMailboxLock('INBOX');
|
|
52
45
|
try {
|
|
53
|
-
// fetch latest message
|
|
54
|
-
// client.mailbox includes information about currently selected mailbox
|
|
55
|
-
// "exists" value is also the largest sequence number available in the mailbox
|
|
46
|
+
// fetch latest message
|
|
56
47
|
let message = await client.fetchOne(client.mailbox.exists, { source: true });
|
|
57
48
|
console.log(message.source.toString());
|
|
58
49
|
|
|
59
50
|
// list subjects for all messages
|
|
60
|
-
// uid value is always included in FETCH response, envelope strings are in unicode.
|
|
61
51
|
for await (let message of client.fetch('1:*', { envelope: true })) {
|
|
62
52
|
console.log(`${message.uid}: ${message.envelope.subject}`);
|
|
63
53
|
}
|
|
64
54
|
} finally {
|
|
65
|
-
//
|
|
55
|
+
// always release the lock
|
|
66
56
|
lock.release();
|
|
67
57
|
}
|
|
68
58
|
|
|
69
|
-
// log out and close connection
|
|
70
59
|
await client.logout();
|
|
71
60
|
};
|
|
72
61
|
|
|
73
|
-
main().catch(
|
|
62
|
+
main().catch(console.error);
|
|
74
63
|
```
|
|
75
64
|
|
|
76
|
-
|
|
65
|
+
See the [Quick Start guide](https://imapflow.com/docs/getting-started/quick-start) for more examples, including Gmail, Outlook, and Yahoo configuration.
|
|
77
66
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
```js
|
|
81
|
-
const { ImapFlow } = require('imapflow');
|
|
82
|
-
const client = new ImapFlow({
|
|
83
|
-
host: 'mail.example.com',
|
|
84
|
-
port: 993,
|
|
85
|
-
secure: true,
|
|
86
|
-
auth: {
|
|
87
|
-
user: 'admin@example.com', // Admin credentials (authentication identity)
|
|
88
|
-
pass: 'adminpassword',
|
|
89
|
-
authzid: 'user@example.com', // User to impersonate (authorization identity)
|
|
90
|
-
loginMethod: 'AUTH=PLAIN' // Must use PLAIN mechanism for authzid
|
|
91
|
-
}
|
|
92
|
-
});
|
|
67
|
+
## Documentation
|
|
93
68
|
|
|
94
|
-
|
|
95
|
-
await client.connect();
|
|
96
|
-
// Now operating on user@example.com's mailbox as admin
|
|
97
|
-
```
|
|
69
|
+
Full documentation is available at **[imapflow.com](https://imapflow.com/docs/)**.
|
|
98
70
|
|
|
99
|
-
|
|
71
|
+
- [Installation](https://imapflow.com/docs/getting-started/installation) - requirements and setup
|
|
72
|
+
- [Quick Start](https://imapflow.com/docs/getting-started/quick-start) - your first ImapFlow application
|
|
73
|
+
- [Basic Usage](https://imapflow.com/docs/guides/basic-usage) - core concepts and patterns
|
|
74
|
+
- [Configuration](https://imapflow.com/docs/guides/configuration) - connection options and settings
|
|
75
|
+
- [Fetching Messages](https://imapflow.com/docs/guides/fetching-messages) - reading email data
|
|
76
|
+
- [Searching](https://imapflow.com/docs/guides/searching) - finding messages with search queries
|
|
77
|
+
- [Mailbox Management](https://imapflow.com/docs/guides/mailbox-management) - creating, renaming, and deleting mailboxes
|
|
78
|
+
- [API Reference](https://imapflow.com/docs/api/imapflow-client) - complete method and event documentation
|
|
100
79
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
[API reference](https://imapflow.com/docs/api/imapflow-client).
|
|
80
|
+
> [!NOTE]
|
|
81
|
+
> If you are looking for a complete email integration solution, ImapFlow was built for [EmailEngine](https://emailengine.app/), a self-hosted email gateway that provides REST API access to IMAP and SMTP accounts.
|
|
104
82
|
|
|
105
83
|
## License
|
|
106
84
|
|
|
107
|
-
|
|
85
|
+
Copyright (c) 2020-2025 Postal Systems OU
|
|
108
86
|
|
|
109
|
-
Licensed under
|
|
87
|
+
Licensed under the MIT license.
|
package/eslint.config.js
CHANGED
|
@@ -1,38 +1,40 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { FlatCompat } = require('@eslint/eslintrc');
|
|
4
3
|
const js = require('@eslint/js');
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
recommendedConfig: js.configs.recommended
|
|
9
|
-
});
|
|
4
|
+
const globals = require('globals');
|
|
5
|
+
const prettierConfig = require('eslint-config-prettier/flat');
|
|
6
|
+
const nodemailerConfig = require('eslint-config-nodemailer');
|
|
10
7
|
|
|
11
8
|
module.exports = [
|
|
12
9
|
{
|
|
13
10
|
ignores: ['node_modules/**', 'examples/**', 'docs/**']
|
|
14
11
|
},
|
|
15
|
-
|
|
12
|
+
js.configs.recommended,
|
|
16
13
|
{
|
|
17
14
|
languageOptions: {
|
|
18
|
-
ecmaVersion:
|
|
15
|
+
ecmaVersion: 2022,
|
|
19
16
|
sourceType: 'script',
|
|
20
|
-
globals: {
|
|
21
|
-
BigInt: 'readonly'
|
|
22
|
-
},
|
|
23
|
-
parser: require('@babel/eslint-parser'),
|
|
24
17
|
parserOptions: {
|
|
25
|
-
|
|
18
|
+
ecmaFeatures: {
|
|
19
|
+
globalReturn: true
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
globals: {
|
|
23
|
+
...globals.node,
|
|
24
|
+
...globals.es2021,
|
|
25
|
+
it: 'readonly',
|
|
26
|
+
describe: 'readonly',
|
|
27
|
+
beforeEach: 'readonly',
|
|
28
|
+
afterEach: 'readonly'
|
|
26
29
|
}
|
|
27
30
|
},
|
|
28
|
-
plugins: {
|
|
29
|
-
'@babel': require('@babel/eslint-plugin')
|
|
30
|
-
},
|
|
31
31
|
rules: {
|
|
32
|
+
...nodemailerConfig.rules,
|
|
32
33
|
'no-await-in-loop': 0,
|
|
33
34
|
'require-atomic-updates': 0
|
|
34
35
|
}
|
|
35
36
|
},
|
|
37
|
+
prettierConfig,
|
|
36
38
|
{
|
|
37
39
|
files: ['eslint.config.js', '.prettierrc.js', '.ncurc.js'],
|
|
38
40
|
rules: {
|
package/lib/imap-flow.js
CHANGED
|
@@ -1018,7 +1018,7 @@ class ImapFlow extends EventEmitter {
|
|
|
1018
1018
|
processedChunks = 0;
|
|
1019
1019
|
|
|
1020
1020
|
let chunk;
|
|
1021
|
-
while ((chunk = this.writeSocket.read()) !== null) {
|
|
1021
|
+
while (this.writeSocket && (chunk = this.writeSocket.read()) !== null) {
|
|
1022
1022
|
if (this._deflate && this._deflate.write(chunk) === false) {
|
|
1023
1023
|
return this._deflate.once('drain', readNext);
|
|
1024
1024
|
}
|
|
@@ -1027,6 +1027,9 @@ class ImapFlow extends EventEmitter {
|
|
|
1027
1027
|
processedChunks++;
|
|
1028
1028
|
if (processedChunks % 100 === 0) {
|
|
1029
1029
|
await new Promise(resolve => setImmediate(resolve));
|
|
1030
|
+
if (!this.writeSocket) {
|
|
1031
|
+
break;
|
|
1032
|
+
}
|
|
1030
1033
|
}
|
|
1031
1034
|
}
|
|
1032
1035
|
|
|
@@ -1042,17 +1045,21 @@ class ImapFlow extends EventEmitter {
|
|
|
1042
1045
|
};
|
|
1043
1046
|
|
|
1044
1047
|
this.writeSocket.on('readable', () => {
|
|
1045
|
-
if (!reading) {
|
|
1048
|
+
if (!reading && this.writeSocket) {
|
|
1046
1049
|
readNext();
|
|
1047
1050
|
}
|
|
1048
1051
|
});
|
|
1049
1052
|
this.writeSocket.on('error', err => {
|
|
1050
|
-
this.socket
|
|
1053
|
+
if (this.socket) {
|
|
1054
|
+
this.socket.emit('error', err);
|
|
1055
|
+
}
|
|
1051
1056
|
});
|
|
1052
1057
|
|
|
1053
1058
|
this._deflate.pipe(this.socket);
|
|
1054
1059
|
this._deflate.on('error', err => {
|
|
1055
|
-
this.socket
|
|
1060
|
+
if (this.socket) {
|
|
1061
|
+
this.socket.emit('error', err);
|
|
1062
|
+
}
|
|
1056
1063
|
});
|
|
1057
1064
|
}
|
|
1058
1065
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.10",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"main": "lib/imap-flow.js",
|
|
6
6
|
"types": "lib/imap-flow.d.ts",
|
|
@@ -27,13 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://imapflow.com/",
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@babel/eslint-parser": "7.28.6",
|
|
31
|
-
"@babel/eslint-plugin": "7.27.1",
|
|
32
|
-
"@babel/plugin-syntax-class-properties": "7.12.13",
|
|
33
|
-
"@babel/preset-env": "7.29.0",
|
|
34
|
-
"@eslint/eslintrc": "3.3.3",
|
|
35
30
|
"@eslint/js": "9.39.2",
|
|
36
|
-
"@types/node": "25.
|
|
31
|
+
"@types/node": "25.3.0",
|
|
37
32
|
"c8": "10.1.3",
|
|
38
33
|
"eslint": "9.39.2",
|
|
39
34
|
"eslint-config-nodemailer": "1.2.0",
|
|
@@ -41,7 +36,7 @@
|
|
|
41
36
|
"grunt": "1.6.1",
|
|
42
37
|
"grunt-cli": "1.5.0",
|
|
43
38
|
"grunt-contrib-nodeunit": "5.0.0",
|
|
44
|
-
"grunt-eslint": "
|
|
39
|
+
"grunt-eslint": "26.0.0",
|
|
45
40
|
"prettier": "3.8.1",
|
|
46
41
|
"proxyquire": "^2.1.3",
|
|
47
42
|
"typescript": "5.9.3"
|
|
@@ -53,8 +48,8 @@
|
|
|
53
48
|
"libbase64": "1.3.0",
|
|
54
49
|
"libmime": "5.3.7",
|
|
55
50
|
"libqp": "2.1.1",
|
|
56
|
-
"nodemailer": "8.0.
|
|
57
|
-
"pino": "10.3.
|
|
51
|
+
"nodemailer": "8.0.1",
|
|
52
|
+
"pino": "10.3.1",
|
|
58
53
|
"socks": "2.8.7"
|
|
59
54
|
}
|
|
60
55
|
}
|
|
@@ -1464,3 +1464,108 @@ module.exports['Connection Edge: connect throws if called twice'] = async test =
|
|
|
1464
1464
|
|
|
1465
1465
|
test.done();
|
|
1466
1466
|
};
|
|
1467
|
+
|
|
1468
|
+
// Helper to create a mock client with compression enabled
|
|
1469
|
+
async function setupCompressedClient() {
|
|
1470
|
+
let client = new ImapFlow({
|
|
1471
|
+
host: 'imap.example.com',
|
|
1472
|
+
port: 993,
|
|
1473
|
+
auth: { user: 'test', pass: 'test' }
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
let mockSocket = new EventEmitter();
|
|
1477
|
+
mockSocket.pipe = dest => dest;
|
|
1478
|
+
mockSocket.unpipe = () => {};
|
|
1479
|
+
mockSocket.destroy = () => {};
|
|
1480
|
+
mockSocket.destroyed = false;
|
|
1481
|
+
client.socket = mockSocket;
|
|
1482
|
+
client.streamer = new EventEmitter();
|
|
1483
|
+
|
|
1484
|
+
client.run = async command => {
|
|
1485
|
+
if (command === 'COMPRESS') {
|
|
1486
|
+
return true;
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
|
|
1490
|
+
await client.compress();
|
|
1491
|
+
return { client, mockSocket };
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
module.exports['Connection Edge: compress writeSocket error forwarded to socket when live'] = async test => {
|
|
1495
|
+
let { client, mockSocket } = await setupCompressedClient();
|
|
1496
|
+
|
|
1497
|
+
let errorReceived = false;
|
|
1498
|
+
mockSocket.on('error', err => {
|
|
1499
|
+
errorReceived = true;
|
|
1500
|
+
test.equal(err.message, 'writeSocket error');
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
client.writeSocket.emit('error', new Error('writeSocket error'));
|
|
1504
|
+
|
|
1505
|
+
test.ok(errorReceived, 'Error should be forwarded to socket');
|
|
1506
|
+
test.done();
|
|
1507
|
+
};
|
|
1508
|
+
|
|
1509
|
+
module.exports['Connection Edge: compress _deflate error forwarded to socket when live'] = async test => {
|
|
1510
|
+
let { client, mockSocket } = await setupCompressedClient();
|
|
1511
|
+
|
|
1512
|
+
let errorReceived = false;
|
|
1513
|
+
mockSocket.on('error', err => {
|
|
1514
|
+
errorReceived = true;
|
|
1515
|
+
test.equal(err.message, 'deflate error');
|
|
1516
|
+
});
|
|
1517
|
+
|
|
1518
|
+
client._deflate.emit('error', new Error('deflate error'));
|
|
1519
|
+
|
|
1520
|
+
test.ok(errorReceived, 'Error should be forwarded to socket');
|
|
1521
|
+
test.done();
|
|
1522
|
+
};
|
|
1523
|
+
|
|
1524
|
+
module.exports['Connection Edge: compress readable after close does not crash'] = async test => {
|
|
1525
|
+
let { client } = await setupCompressedClient();
|
|
1526
|
+
|
|
1527
|
+
// Save reference before close() nulls it
|
|
1528
|
+
let writeSocket = client.writeSocket;
|
|
1529
|
+
|
|
1530
|
+
client.close();
|
|
1531
|
+
|
|
1532
|
+
// Emit readable on the saved reference after close has nulled this.writeSocket
|
|
1533
|
+
// This should not throw (guards at lines 1048 and 1021)
|
|
1534
|
+
test.doesNotThrow(() => {
|
|
1535
|
+
writeSocket.emit('readable');
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
test.done();
|
|
1539
|
+
};
|
|
1540
|
+
|
|
1541
|
+
module.exports['Connection Edge: compress writeSocket error after close does not crash'] = async test => {
|
|
1542
|
+
let { client } = await setupCompressedClient();
|
|
1543
|
+
|
|
1544
|
+
let writeSocket = client.writeSocket;
|
|
1545
|
+
|
|
1546
|
+
client.close();
|
|
1547
|
+
|
|
1548
|
+
// Emit error on saved writeSocket after close has nulled this.socket
|
|
1549
|
+
// This should not throw (guard at lines 1053-1055)
|
|
1550
|
+
test.doesNotThrow(() => {
|
|
1551
|
+
writeSocket.emit('error', new Error('late writeSocket error'));
|
|
1552
|
+
});
|
|
1553
|
+
|
|
1554
|
+
test.done();
|
|
1555
|
+
};
|
|
1556
|
+
|
|
1557
|
+
module.exports['Connection Edge: compress _deflate error after close does not crash'] = async test => {
|
|
1558
|
+
let { client } = await setupCompressedClient();
|
|
1559
|
+
|
|
1560
|
+
let deflate = client._deflate;
|
|
1561
|
+
|
|
1562
|
+
client.close();
|
|
1563
|
+
|
|
1564
|
+
// Emit error on saved _deflate after close has nulled this.socket
|
|
1565
|
+
// This should not throw (guard at lines 1060-1062)
|
|
1566
|
+
test.doesNotThrow(() => {
|
|
1567
|
+
deflate.emit('error', new Error('late deflate error'));
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1570
|
+
test.done();
|
|
1571
|
+
};
|
package/.babelrc
DELETED
package/.eslintrc
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"rules": {
|
|
3
|
-
"no-await-in-loop": 0,
|
|
4
|
-
"require-atomic-updates": 0
|
|
5
|
-
},
|
|
6
|
-
"globals": {
|
|
7
|
-
"BigInt": true
|
|
8
|
-
},
|
|
9
|
-
"extends": ["nodemailer", "prettier"],
|
|
10
|
-
"parser": "@babel/eslint-parser",
|
|
11
|
-
"parserOptions": {
|
|
12
|
-
"ecmaVersion": 2020,
|
|
13
|
-
"sourceType": "script"
|
|
14
|
-
},
|
|
15
|
-
"plugins": ["@babel"]
|
|
16
|
-
}
|