emailengine-app 2.63.2 → 2.63.4

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,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.63.4](https://github.com/postalsys/emailengine/compare/v2.63.3...v2.63.4) (2026-03-09)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * guard null imapClient dereferences during async operations ([671bcee](https://github.com/postalsys/emailengine/commit/671bcee4e81c7c0d6421617c3e25ec6906cb1abb))
9
+ * prevent null dereference crash in getImapConnection during connection drops ([6a356fd](https://github.com/postalsys/emailengine/commit/6a356fd20e1412a070c26c093108c4ea2a722a5c))
10
+ * throw instead of silent return in select() null guard, revert redundant optional chaining ([cb7db50](https://github.com/postalsys/emailengine/commit/cb7db5024e0b00f8a773328dd5e0009067bed0b6))
11
+ * update tests for getCurrentListing null guard changes ([a644f9c](https://github.com/postalsys/emailengine/commit/a644f9c1c3f3e4cbce388e43bfa319f11f4a469c))
12
+
13
+ ## [2.63.3](https://github.com/postalsys/emailengine/compare/v2.63.2...v2.63.3) (2026-03-05)
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * handle undici HeadersTimeoutError as transient in Pub/Sub and OAuth2 paths ([8b3b698](https://github.com/postalsys/emailengine/commit/8b3b69880ab09119df4ca377d085db261eae2763))
19
+ * prevent IMAP worker crash on ImapFlow unhandled rejection during IDLE recovery ([86ebb02](https://github.com/postalsys/emailengine/commit/86ebb02c321ffa758966af1957509c10e20a35fc))
20
+ * prevent transient network errors during OAuth2 token refresh from being misclassified as auth failures ([38fa212](https://github.com/postalsys/emailengine/commit/38fa212092847b727ac1d02541103c032a983dc7))
21
+ * retry transient network errors in Gmail and Outlook API request functions ([08ea0da](https://github.com/postalsys/emailengine/commit/08ea0daecaec73c58a91ac07df5d0f0ffc083a2f))
22
+ * treat DNS errors as transient in Google Pub/Sub polling loop ([ec33673](https://github.com/postalsys/emailengine/commit/ec33673b47242fd7156b4641c9c4924a33eaf9e5))
23
+
3
24
  ## [2.63.2](https://github.com/postalsys/emailengine/compare/v2.63.1...v2.63.2) (2026-03-03)
4
25
 
5
26
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "creationTime": "2026-03-02T15:45:59.000000",
2
+ "creationTime": "2026-03-06T15:46:26.000000",
3
3
  "prefixes": [
4
4
  {
5
5
  "ipv6Prefix": "2001:4860:4801:2008::/64"
@@ -538,6 +538,28 @@ class BaseClient {
538
538
  });
539
539
  accessToken = accountData.oauth2.accessToken;
540
540
  } catch (err) {
541
+ // Transient network errors are not authentication failures
542
+ let isNetworkError =
543
+ err.code === 'ENOTFOUND' ||
544
+ err.code === 'ETIMEDOUT' ||
545
+ err.code === 'ECONNREFUSED' ||
546
+ err.code === 'ECONNRESET' ||
547
+ err.code === 'EAI_AGAIN' ||
548
+ err.code === 'UND_ERR_CONNECT_TIMEOUT' ||
549
+ err.code === 'UND_ERR_SOCKET' ||
550
+ err.code === 'UND_ERR_HEADERS_TIMEOUT';
551
+
552
+ if (isNetworkError) {
553
+ ctx.logger.error({
554
+ msg: 'Network error during OAuth2 token refresh',
555
+ account: accountObject.account,
556
+ code: err.code,
557
+ err
558
+ });
559
+ // Do not mark as authenticationFailed, let the caller handle as a connection error
560
+ throw err;
561
+ }
562
+
541
563
  err.authenticationFailed = true;
542
564
  let notifyData = {
543
565
  response: err.message,
@@ -12,6 +12,18 @@ const LIST_BATCH_SIZE = 10;
12
12
  const MAX_RETRY_ATTEMPTS = 3;
13
13
  const RETRY_BASE_DELAY = 1000; // 1 second base delay
14
14
 
15
+ // Network-level errors that are transient and should be retried
16
+ const TRANSIENT_NETWORK_CODES = new Set([
17
+ 'ENOTFOUND',
18
+ 'EAI_AGAIN',
19
+ 'ETIMEDOUT',
20
+ 'ECONNRESET',
21
+ 'ECONNREFUSED',
22
+ 'UND_ERR_SOCKET',
23
+ 'UND_ERR_CONNECT_TIMEOUT',
24
+ 'UND_ERR_HEADERS_TIMEOUT'
25
+ ]);
26
+
15
27
  // Gmail API error code mapping to internal error codes
16
28
  // https://developers.google.com/gmail/api/reference/rest#error-codes
17
29
  const GMAIL_ERROR_MAP = {
@@ -117,6 +129,29 @@ async function request(context, url, method, payload, options = {}) {
117
129
  } catch (err) {
118
130
  lastError = err;
119
131
 
132
+ // Check if this is a transient network error
133
+ if (TRANSIENT_NETWORK_CODES.has(err.code) && attempt < maxRetries) {
134
+ const delay = RETRY_BASE_DELAY * Math.pow(2, attempt) + Math.random() * 500;
135
+
136
+ context.logger.warn({
137
+ msg: 'Transient network error during Gmail API request, retrying',
138
+ account: context.account,
139
+ attempt: attempt + 1,
140
+ maxRetries,
141
+ delay,
142
+ code: err.code
143
+ });
144
+
145
+ metricsMeta({ account: context.account }, context.logger, 'oauth2ApiRequest', 'inc', {
146
+ status: 'network_error',
147
+ provider: 'gmail',
148
+ statusCode: '0'
149
+ });
150
+
151
+ await new Promise(resolve => setTimeout(resolve, delay));
152
+ continue;
153
+ }
154
+
120
155
  // Check if this is a rate limit error
121
156
  if (isRateLimitError(err) && attempt < maxRetries) {
122
157
  const delay = calculateRetryDelay(err, attempt);
@@ -94,7 +94,10 @@ class Mailbox {
94
94
  getMailboxStatus(connectionClient) {
95
95
  connectionClient = connectionClient || this.connection.imapClient;
96
96
  if (!connectionClient) {
97
- throw new Error('IMAP connection not available');
97
+ let err = new Error('IMAP connection not available');
98
+ err.code = 'IMAPConnectionClosing';
99
+ err.statusCode = 503;
100
+ throw err;
98
101
  }
99
102
 
100
103
  let mailboxInfo = connectionClient.mailbox;
@@ -490,6 +493,12 @@ class Mailbox {
490
493
  * @param {Boolean} skipIdle - Don't start IDLE after selecting
491
494
  */
492
495
  async select(skipIdle) {
496
+ if (!this.connection.imapClient) {
497
+ let err = new Error('IMAP connection not available');
498
+ err.code = 'IMAPConnectionClosing';
499
+ err.statusCode = 503;
500
+ throw err;
501
+ }
493
502
  const currentLock = this.connection.imapClient.currentLock;
494
503
  // Avoid interfering with any active operations
495
504
  if (currentLock) {
@@ -520,7 +529,7 @@ class Mailbox {
520
529
 
521
530
  // Check if we still need to select after getting the lock
522
531
  // Another operation might have already selected this mailbox while we were waiting
523
- if (this.connection.imapClient.mailbox && this.connection.imapClient.mailbox.path === this.path) {
532
+ if (this.connection.imapClient?.mailbox && this.connection.imapClient.mailbox.path === this.path) {
524
533
  this.logger.trace({
525
534
  msg: 'Mailbox already selected after lock acquired',
526
535
  path: this.path
@@ -551,7 +560,10 @@ class Mailbox {
551
560
  connectionClient = connectionClient || this.connection.imapClient;
552
561
 
553
562
  if (!connectionClient) {
554
- throw new Error('IMAP connection not available');
563
+ let err = new Error('IMAP connection not available');
564
+ err.code = 'IMAPConnectionClosing';
565
+ err.statusCode = 503;
566
+ throw err;
555
567
  }
556
568
 
557
569
  let lock = await connectionClient.getMailboxLock(this.path, options || {});
@@ -613,7 +625,13 @@ class Mailbox {
613
625
  return false;
614
626
  })
615
627
  .then(() => this.select())
616
- .catch(err => this.logger.error({ msg: 'Sync error', err }));
628
+ .catch(err => {
629
+ if (err.code === 'IMAPConnectionClosing') {
630
+ this.logger.debug({ msg: 'Sync skipped, connection closing', err });
631
+ } else {
632
+ this.logger.error({ msg: 'Sync error', err });
633
+ }
634
+ });
617
635
  }, 1000);
618
636
  }
619
637
 
@@ -1090,7 +1108,7 @@ class Mailbox {
1090
1108
 
1091
1109
  // Resolve Gmail category for inbox messages
1092
1110
  if (
1093
- this.connection.imapClient.capabilities.has('X-GM-EXT-1') &&
1111
+ this.connection.imapClient?.capabilities?.has('X-GM-EXT-1') &&
1094
1112
  this.isAllMail &&
1095
1113
  messageInfo.labels &&
1096
1114
  messageInfo.labels.includes('\\Inbox') &&
@@ -2069,7 +2087,7 @@ class Mailbox {
2069
2087
  }
2070
2088
 
2071
2089
  // Partial sync if CONDSTORE indicates only new messages or flag changes
2072
- if (canUseCondstorePartialSync(this.connection.imapClient, storedStatus, mailboxStatus)) {
2090
+ if (this.connection.imapClient && canUseCondstorePartialSync(this.connection.imapClient, storedStatus, mailboxStatus)) {
2073
2091
  return await this.partialSync(storedStatus);
2074
2092
  }
2075
2093
 
@@ -197,7 +197,10 @@ class SyncOperations {
197
197
  this.mailbox.syncing = true;
198
198
  try {
199
199
  if (!this.connection.imapClient) {
200
- throw new Error('IMAP connection not available');
200
+ let err = new Error('IMAP connection not available');
201
+ err.code = 'IMAPConnectionClosing';
202
+ err.statusCode = 503;
203
+ throw err;
201
204
  }
202
205
 
203
206
  let knownUidNext = typeof storedStatus.uidNext === 'number' ? storedStatus.uidNext || 1 : 1;
@@ -351,7 +354,10 @@ class SyncOperations {
351
354
  this.mailbox.syncing = true;
352
355
  try {
353
356
  if (!this.connection.imapClient) {
354
- throw new Error('IMAP connection not available');
357
+ let err = new Error('IMAP connection not available');
358
+ err.code = 'IMAPConnectionClosing';
359
+ err.statusCode = 503;
360
+ throw err;
355
361
  }
356
362
 
357
363
  let fields = { uid: true, flags: true, modseq: true, emailId: true, labels: true, internalDate: true };
@@ -378,7 +384,10 @@ class SyncOperations {
378
384
  let imapClient = this.connection.imapClient;
379
385
  if (!imapClient || !imapClient.usable) {
380
386
  this.logger.error({ msg: 'IMAP client not available for partial sync' });
381
- throw new Error('IMAP connection not available');
387
+ let err = new Error('IMAP connection not available');
388
+ err.code = 'IMAPConnectionClosing';
389
+ err.statusCode = 503;
390
+ throw err;
382
391
  }
383
392
 
384
393
  try {
@@ -556,7 +565,10 @@ class SyncOperations {
556
565
  const imapClient = this.connection.imapClient;
557
566
  if (!imapClient || !imapClient.usable) {
558
567
  this.logger.error({ msg: 'IMAP client not available for FETCH' });
559
- throw new Error('IMAP connection not available');
568
+ let err = new Error('IMAP connection not available');
569
+ err.code = 'IMAPConnectionClosing';
570
+ err.statusCode = 503;
571
+ throw err;
560
572
  }
561
573
 
562
574
  try {
@@ -728,7 +740,7 @@ class SyncOperations {
728
740
 
729
741
  // Verify we're still on the correct mailbox after the delay
730
742
  // Another operation might have changed the mailbox while we were waiting
731
- const currentMailbox = this.connection.imapClient.mailbox;
743
+ const currentMailbox = this.connection.imapClient?.mailbox;
732
744
  if (!currentMailbox || currentMailbox.path !== this.mailbox.path) {
733
745
  this.logger.error({
734
746
  msg: 'Mailbox changed during retry delay, aborting sync',
@@ -191,6 +191,12 @@ class IMAPClient extends BaseClient {
191
191
  let syncing = this.syncing || ['init', 'connecting', 'syncing'].includes(this.state);
192
192
  if (!noPool && (!syncing || !allowSecondary)) {
193
193
  // Return the primary connection for most operations
194
+ if (!this.imapClient) {
195
+ let err = new Error('IMAP connection not available');
196
+ err.code = 'IMAPConnectionClosing';
197
+ err.statusCode = 503;
198
+ throw err;
199
+ }
194
200
  return this.imapClient;
195
201
  }
196
202
 
@@ -202,14 +208,19 @@ class IMAPClient extends BaseClient {
202
208
  if (connectionClient && connectionClient.usable) {
203
209
  connectionOptions.connectionClient = connectionClient;
204
210
  return connectionClient;
205
- } else {
206
- // fall back to default connection
207
- return this.imapClient;
208
211
  }
209
212
  } catch (err) {
210
213
  this.logger.error({ msg: 'Failed to acquire command connection', reason, err });
211
- return this.imapClient;
212
214
  }
215
+
216
+ // Fall back to primary connection
217
+ if (!this.imapClient) {
218
+ let err = new Error('IMAP connection not available');
219
+ err.code = 'IMAPConnectionClosing';
220
+ err.statusCode = 503;
221
+ throw err;
222
+ }
223
+ return this.imapClient;
213
224
  }
214
225
 
215
226
  /**
@@ -535,14 +546,6 @@ class IMAPClient extends BaseClient {
535
546
  this.checkIMAPConnection(connectionOptions);
536
547
 
537
548
  const connectionClient = await this.getImapConnection(connectionOptions, 'getCurrentListing');
538
- if (!connectionClient) {
539
- if (this.imapClient) {
540
- this.imapClient.close();
541
- }
542
- let error = new Error('Failed to get connection');
543
- error.code = 'ConnectionError';
544
- throw error;
545
- }
546
549
 
547
550
  let accountData = await this.accountObject.loadAccountData();
548
551
 
@@ -802,7 +805,11 @@ class IMAPClient extends BaseClient {
802
805
  try {
803
806
  await mailbox.onOpen(event);
804
807
  } catch (err) {
805
- imapClient.log.error({ msg: 'Open error', err });
808
+ if (err.code === 'IMAPConnectionClosing') {
809
+ imapClient.log.debug({ msg: 'Open skipped, connection closing', err });
810
+ } else {
811
+ imapClient.log.error({ msg: 'Open error', err });
812
+ }
806
813
  }
807
814
  });
808
815
 
@@ -7,6 +7,18 @@ const { OUTLOOK_MAX_BATCH_SIZE, OUTLOOK_MAX_RETRY_ATTEMPTS, OUTLOOK_RETRY_BASE_D
7
7
  // Maximum number of operations in a single batch request to Microsoft Graph API
8
8
  const MAX_BATCH_SIZE = OUTLOOK_MAX_BATCH_SIZE;
9
9
 
10
+ // Network-level errors that are transient and should be retried
11
+ const TRANSIENT_NETWORK_CODES = new Set([
12
+ 'ENOTFOUND',
13
+ 'EAI_AGAIN',
14
+ 'ETIMEDOUT',
15
+ 'ECONNRESET',
16
+ 'ECONNREFUSED',
17
+ 'UND_ERR_SOCKET',
18
+ 'UND_ERR_CONNECT_TIMEOUT',
19
+ 'UND_ERR_HEADERS_TIMEOUT'
20
+ ]);
21
+
10
22
  // MS Graph API error code mapping to internal error codes
11
23
  // https://learn.microsoft.com/en-us/graph/errors
12
24
  const GRAPH_ERROR_MAP = {
@@ -163,6 +175,25 @@ async function requestWithRetry(context, url, method, payload, options = {}) {
163
175
  try {
164
176
  return await request(context, url, method, payload, options);
165
177
  } catch (err) {
178
+ // Retry on transient network errors
179
+ if (TRANSIENT_NETWORK_CODES.has(err.code) && attempt < maxRetries) {
180
+ lastError = err;
181
+ const delay = Math.min(OUTLOOK_RETRY_BASE_DELAY * Math.pow(2, attempt), OUTLOOK_RETRY_MAX_DELAY);
182
+
183
+ context.logger.warn({
184
+ msg: 'Transient network error during Graph API request, retrying',
185
+ account: context.account,
186
+ attempt: attempt + 1,
187
+ maxRetries,
188
+ delay,
189
+ code: err.code,
190
+ url
191
+ });
192
+
193
+ await new Promise(resolve => setTimeout(resolve, delay * 1000));
194
+ continue;
195
+ }
196
+
166
197
  // Only retry on 429 (rate limit) errors
167
198
  if (err.oauthRequest?.status !== 429 || attempt === maxRetries) {
168
199
  throw err;
@@ -203,7 +203,18 @@ class PubSubInstance {
203
203
  await oauth2Apps.setMeta(this.app, { pubSubFlag: null });
204
204
  } catch (err) {
205
205
  // Transient network errors are expected for long-polling connections
206
- if (['ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED', 'UND_ERR_SOCKET', 'UND_ERR_CONNECT_TIMEOUT'].includes(err.code)) {
206
+ if (
207
+ [
208
+ 'ENOTFOUND',
209
+ 'EAI_AGAIN',
210
+ 'ETIMEDOUT',
211
+ 'ECONNRESET',
212
+ 'ECONNREFUSED',
213
+ 'UND_ERR_SOCKET',
214
+ 'UND_ERR_CONNECT_TIMEOUT',
215
+ 'UND_ERR_HEADERS_TIMEOUT'
216
+ ].includes(err.code)
217
+ ) {
207
218
  logger.warn({ msg: 'Transient error pulling subscription messages', app: this.app, code: err.code });
208
219
  return;
209
220
  }
@@ -11,6 +11,17 @@ const Lock = require('ioredfour');
11
11
  const getSecret = require('./get-secret');
12
12
  const { parentPort } = require('worker_threads');
13
13
 
14
+ const TRANSIENT_NETWORK_CODES = new Set([
15
+ 'ENOTFOUND',
16
+ 'EAI_AGAIN',
17
+ 'ETIMEDOUT',
18
+ 'ECONNRESET',
19
+ 'ECONNREFUSED',
20
+ 'UND_ERR_SOCKET',
21
+ 'UND_ERR_CONNECT_TIMEOUT',
22
+ 'UND_ERR_HEADERS_TIMEOUT'
23
+ ]);
24
+
14
25
  /**
15
26
  * Record metrics for OAuth2 token operations
16
27
  * Works in both main thread and worker threads
@@ -922,6 +933,10 @@ class OAuth2AppsHandler {
922
933
  {name: 'projects/...'}
923
934
  */
924
935
  } catch (err) {
936
+ if (TRANSIENT_NETWORK_CODES.has(err.code)) {
937
+ logger.warn({ msg: 'Network error checking Pub/Sub topic', app: appData.id, code: err.code });
938
+ throw err;
939
+ }
925
940
  switch (err?.oauthRequest?.response?.error?.code) {
926
941
  case 403:
927
942
  // no permissions
@@ -964,6 +979,10 @@ class OAuth2AppsHandler {
964
979
 
965
980
  results.pubSubTopic = topicName;
966
981
  } catch (err) {
982
+ if (TRANSIENT_NETWORK_CODES.has(err.code)) {
983
+ logger.warn({ msg: 'Network error creating Pub/Sub topic', app: appData.id, code: err.code });
984
+ throw err;
985
+ }
967
986
  switch (err?.oauthRequest?.response?.error?.code) {
968
987
  case 403:
969
988
  // no permissions
@@ -999,6 +1018,10 @@ class OAuth2AppsHandler {
999
1018
  {name: 'projects/...'}
1000
1019
  */
1001
1020
  } catch (err) {
1021
+ if (TRANSIENT_NETWORK_CODES.has(err.code)) {
1022
+ logger.warn({ msg: 'Network error checking Pub/Sub subscription', app: appData.id, code: err.code });
1023
+ throw err;
1024
+ }
1002
1025
  switch (err?.oauthRequest?.response?.error?.code) {
1003
1026
  case 403:
1004
1027
  // no permissions
@@ -1042,6 +1065,10 @@ class OAuth2AppsHandler {
1042
1065
 
1043
1066
  results.pubSubSubscription = subscriptionName;
1044
1067
  } catch (err) {
1068
+ if (TRANSIENT_NETWORK_CODES.has(err.code)) {
1069
+ logger.warn({ msg: 'Network error creating Pub/Sub subscription', app: appData.id, code: err.code });
1070
+ throw err;
1071
+ }
1045
1072
  switch (err?.oauthRequest?.response?.error?.code) {
1046
1073
  case 403:
1047
1074
  // no permissions
@@ -1087,6 +1114,10 @@ class OAuth2AppsHandler {
1087
1114
  }
1088
1115
  */
1089
1116
  } catch (err) {
1117
+ if (TRANSIENT_NETWORK_CODES.has(err.code)) {
1118
+ logger.warn({ msg: 'Network error checking Pub/Sub IAM policy', app: appData.id, code: err.code });
1119
+ throw err;
1120
+ }
1090
1121
  switch (err?.oauthRequest?.response?.error?.code) {
1091
1122
  case 403:
1092
1123
  // no permissions
@@ -1130,6 +1161,10 @@ class OAuth2AppsHandler {
1130
1161
  { partial: true }
1131
1162
  );
1132
1163
  } catch (err) {
1164
+ if (TRANSIENT_NETWORK_CODES.has(err.code)) {
1165
+ logger.warn({ msg: 'Network error setting Pub/Sub IAM policy', app: appData.id, code: err.code });
1166
+ throw err;
1167
+ }
1133
1168
  switch (err?.oauthRequest?.response?.error?.code) {
1134
1169
  case 403:
1135
1170
  // no permissions
package/lib/tools.js CHANGED
@@ -2069,6 +2069,7 @@ RHH2IL/f3lipzTFk
2069
2069
  5q3TdjzMplq0+yI7
2070
2070
  nL6uRkZxgug/JFyl
2071
2071
  uSY0M+yfff4Op/HP
2072
+ tpADMZ5Ir6bmwexH
2072
2073
  h7Squx/sKZNkDat0
2073
2074
  elb9jWFuO3UHnphx
2074
2075
  //m57pmYUuiv84pZ
@@ -2102,6 +2103,10 @@ r/CVn8qB2xdyVXCg
2102
2103
  iSeRSmpWBZGpkTZi
2103
2104
  ZZkXc6t+ZDyuhEoO
2104
2105
  vPHQ3BYeEr+9DSlW
2106
+ 5ChBAv/YX3PWk/Wz
2107
+ YjuOzxqQEGdKLC0Q
2108
+ nmu7gC+IxPl4AgjV
2109
+ 43Du3vhZaEGambkT
2105
2110
  vIwyKmyMuykMUPeA
2106
2111
  RTIy3POEF2gZADpU
2107
2112
  pqvSQnQ/jx8sYGi4
@@ -2144,6 +2149,7 @@ AW6pdfk8U1/EXPdY
2144
2149
  Q/6B3a679QVyRrsE
2145
2150
  EsbWnuADOq9qe/EZ
2146
2151
  1xyU3RkTVo/iQd3J
2152
+ +HW4BuGQxaXdahTi
2147
2153
  YfUSxQtq1+kpwW/W
2148
2154
  QodzxvoJ6NPGhCgW
2149
2155
  5/VK+O950efBio0t
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emailengine-app",
3
- "version": "2.63.2",
3
+ "version": "2.63.4",
4
4
  "private": false,
5
5
  "productTitle": "EmailEngine",
6
6
  "description": "Email Sync Engine",
@@ -52,15 +52,15 @@
52
52
  "@hapi/boom": "10.0.1",
53
53
  "@hapi/cookie": "12.0.1",
54
54
  "@hapi/crumb": "9.0.1",
55
- "@hapi/hapi": "21.4.6",
55
+ "@hapi/hapi": "21.4.7",
56
56
  "@hapi/inert": "7.1.0",
57
57
  "@hapi/vision": "7.0.3",
58
58
  "@phc/pbkdf2": "1.1.14",
59
59
  "@postalsys/bounce-classifier": "^2.0.0",
60
60
  "@postalsys/certs": "1.0.12",
61
61
  "@postalsys/ee-client": "1.3.0",
62
- "@postalsys/email-ai-tools": "1.11.3",
63
- "@postalsys/email-text-tools": "2.4.1",
62
+ "@postalsys/email-ai-tools": "1.11.4",
63
+ "@postalsys/email-text-tools": "2.4.2",
64
64
  "@postalsys/gettext": "4.1.1",
65
65
  "@postalsys/joi-messages": "1.0.5",
66
66
  "@postalsys/templates": "2.0.0",
@@ -68,7 +68,7 @@
68
68
  "@zone-eu/wild-config": "1.7.3",
69
69
  "ace-builds": "1.43.6",
70
70
  "base32.js": "0.1.0",
71
- "bullmq": "5.70.1",
71
+ "bullmq": "5.70.4",
72
72
  "compare-versions": "6.1.1",
73
73
  "dotenv": "17.3.1",
74
74
  "encoding-japanese": "2.2.0",
@@ -82,7 +82,7 @@
82
82
  "html-to-text": "9.0.5",
83
83
  "ical.js": "1.5.0",
84
84
  "iconv-lite": "0.7.2",
85
- "imapflow": "1.2.11",
85
+ "imapflow": "1.2.13",
86
86
  "ioredfour": "1.4.0",
87
87
  "ioredis": "5.10.0",
88
88
  "ipaddr.js": "2.3.0",
@@ -92,13 +92,13 @@
92
92
  "libmime": "5.3.7",
93
93
  "libqp": "2.1.1",
94
94
  "license-checker": "25.0.1",
95
- "mailparser": "3.9.3",
95
+ "mailparser": "3.9.4",
96
96
  "marked": "9.1.6",
97
97
  "minimist": "1.2.8",
98
98
  "msgpack5": "6.0.2",
99
99
  "murmurhash": "2.0.1",
100
100
  "nanoid": "3.3.8",
101
- "nodemailer": "8.0.1",
101
+ "nodemailer": "8.0.2",
102
102
  "pino": "10.3.1",
103
103
  "popper.js": "1.16.1",
104
104
  "prom-client": "15.1.3",
@@ -118,7 +118,7 @@
118
118
  "@eslint/js": "10.0.1",
119
119
  "chai": "4.3.10",
120
120
  "eerawlog": "1.5.1",
121
- "eslint": "10.0.2",
121
+ "eslint": "10.0.3",
122
122
  "grunt": "1.6.1",
123
123
  "grunt-cli": "1.5.0",
124
124
  "grunt-shell-spawn": "0.5.0",