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.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "1.2.9"
2
+ ".": "1.2.10"
3
3
  }
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
- ImapFlow is a modern and easy-to-use IMAP client library for Node.js.
3
+ Modern and easy-to-use IMAP client library for Node.js.
4
4
 
5
- > [!NOTE]
6
- > Managing an IMAP connection is cool, but if you are only looking for an easy way to integrate email accounts, then ImapFlow was built for [EmailEngine Email API](https://emailengine.app/). It's a self-hosted software that converts all IMAP accounts to easy-to-use REST interfaces.
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
+ [![npm](https://img.shields.io/npm/v/imapflow)](https://www.npmjs.com/package/imapflow)
6
+ [![license](https://img.shields.io/npm/l/imapflow)](https://github.com/postalsys/imapflow/blob/master/LICENSE)
11
7
 
12
- ## Source
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
- Source code is available from [Github](https://github.com/postalsys/imapflow).
10
+ ## Features
15
11
 
16
- ## Usage
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
- First install the module from npm:
20
+ ## Installation
19
21
 
20
- ```
22
+ ```bash
21
23
  npm install imapflow
22
24
  ```
23
25
 
24
- next import the ImapFlow class into your script:
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: 'ethereal.email',
32
+ host: 'imap.example.com',
38
33
  port: 993,
39
34
  secure: true,
40
35
  auth: {
41
- user: 'garland.mcclure71@ethereal.email',
42
- pass: 'mW6e4wWWnEd3H4hT5B'
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 source
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
- // Make sure lock is released, otherwise next `getMailboxLock()` never returns
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(err => console.error(err));
62
+ main().catch(console.error);
74
63
  ```
75
64
 
76
- ### Admin Impersonation / Delegation (SASL PLAIN with authzid)
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
- ImapFlow supports admin impersonation for mail systems like Zimbra that allow administrators to access user mailboxes. This is done using the SASL PLAIN mechanism with an authorization identity (`authzid`).
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
- // Connection will authenticate as admin but authorize as the specified user
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
- **Note:** The `authzid` parameter only works with the `AUTH=PLAIN` mechanism. The server must support admin delegation/impersonation for this to work.
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
- ## Documentation
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
- © 2020-2025 Postal Systems OÜ
85
+ Copyright (c) 2020-2025 Postal Systems OU
108
86
 
109
- Licensed under **MIT-license**
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 compat = new FlatCompat({
7
- baseDirectory: __dirname,
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
- ...compat.extends('nodemailer', 'prettier'),
12
+ js.configs.recommended,
16
13
  {
17
14
  languageOptions: {
18
- ecmaVersion: 2020,
15
+ ecmaVersion: 2022,
19
16
  sourceType: 'script',
20
- globals: {
21
- BigInt: 'readonly'
22
- },
23
- parser: require('@babel/eslint-parser'),
24
17
  parserOptions: {
25
- requireConfigFile: false
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.emit('error', err);
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.emit('error', err);
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.9",
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.2.1",
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": "24.3.0",
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.0",
57
- "pino": "10.3.0",
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
@@ -1,6 +0,0 @@
1
- {
2
- "presets": ["@babel/env"],
3
- "plugins": [
4
- "@babel/plugin-syntax-class-properties"
5
- ]
6
- }
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
- }