emailengine-app 2.61.5 → 2.62.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/data/google-crawlers.json +1 -1
  3. package/lib/account.js +20 -7
  4. package/lib/api-routes/account-routes.js +28 -5
  5. package/lib/api-routes/chat-routes.js +1 -1
  6. package/lib/api-routes/export-routes.js +316 -0
  7. package/lib/api-routes/message-routes.js +28 -23
  8. package/lib/api-routes/template-routes.js +28 -7
  9. package/lib/arf-detect.js +1 -1
  10. package/lib/consts.js +16 -0
  11. package/lib/db.js +3 -0
  12. package/lib/email-client/base-client.js +6 -4
  13. package/lib/email-client/gmail-client.js +204 -33
  14. package/lib/email-client/imap/mailbox.js +99 -8
  15. package/lib/email-client/imap/subconnection.js +5 -5
  16. package/lib/email-client/imap-client.js +76 -16
  17. package/lib/email-client/message-builder.js +3 -1
  18. package/lib/email-client/notification-handler.js +12 -9
  19. package/lib/email-client/outlook-client.js +362 -69
  20. package/lib/email-client/smtp-pool-manager.js +1 -1
  21. package/lib/export.js +528 -0
  22. package/lib/oauth/gmail.js +21 -13
  23. package/lib/oauth/mail-ru.js +23 -10
  24. package/lib/oauth/outlook.js +26 -16
  25. package/lib/oauth/pubsub/google.js +5 -0
  26. package/lib/routes-ui.js +235 -1
  27. package/lib/schemas.js +260 -80
  28. package/lib/stream-encrypt.js +263 -0
  29. package/lib/tools.js +30 -4
  30. package/lib/ui-routes/account-routes.js +23 -0
  31. package/lib/ui-routes/admin-config-routes.js +11 -4
  32. package/lib/ui-routes/admin-entities-routes.js +18 -0
  33. package/lib/webhooks.js +16 -20
  34. package/package.json +16 -16
  35. package/sbom.json +1 -1
  36. package/server.js +41 -5
  37. package/static/js/ace/ace.js +1 -1
  38. package/static/js/ace/ext-language_tools.js +1 -1
  39. package/static/licenses.html +52 -62
  40. package/translations/de.mo +0 -0
  41. package/translations/de.po +63 -36
  42. package/translations/en.mo +0 -0
  43. package/translations/en.po +64 -37
  44. package/translations/et.mo +0 -0
  45. package/translations/et.po +63 -36
  46. package/translations/fr.mo +0 -0
  47. package/translations/fr.po +63 -36
  48. package/translations/ja.mo +0 -0
  49. package/translations/ja.po +63 -36
  50. package/translations/messages.pot +80 -47
  51. package/translations/nl.mo +0 -0
  52. package/translations/nl.po +63 -36
  53. package/translations/pl.mo +0 -0
  54. package/translations/pl.po +63 -36
  55. package/views/accounts/account.hbs +375 -2
  56. package/views/config/service.hbs +35 -0
  57. package/workers/api.js +123 -44
  58. package/workers/documents.js +1 -0
  59. package/workers/export.js +926 -0
  60. package/workers/imap.js +29 -0
  61. package/workers/submit.js +25 -5
  62. package/workers/webhooks.js +11 -2
@@ -0,0 +1,263 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const { Transform } = require('stream');
5
+
6
+ const MAGIC = Buffer.from('EE01');
7
+ const VERSION = 1;
8
+ const CHUNK_SIZE = 64 * 1024;
9
+ const IV_LENGTH = 12;
10
+ const AUTH_TAG_LENGTH = 16;
11
+ const SALT_LENGTH = 16;
12
+ const HEADER_SIZE = MAGIC.length + 4 + 4 + SALT_LENGTH;
13
+ const MAX_CHUNK_SIZE = CHUNK_SIZE * 4;
14
+
15
+ function deriveKeyAsync(password, salt) {
16
+ return new Promise((resolve, reject) => {
17
+ crypto.scrypt(password, salt, 32, (err, key) => {
18
+ if (err) reject(err);
19
+ else resolve(key);
20
+ });
21
+ });
22
+ }
23
+
24
+ class EncryptStream extends Transform {
25
+ constructor({ key, salt }) {
26
+ super();
27
+ this.key = key;
28
+ this.salt = salt;
29
+ this.buffer = Buffer.alloc(0);
30
+ this.headerWritten = false;
31
+ }
32
+
33
+ _writeHeader() {
34
+ if (this.headerWritten) return;
35
+
36
+ const versionBuf = Buffer.alloc(4);
37
+ versionBuf.writeUInt32LE(VERSION);
38
+
39
+ const chunkSizeBuf = Buffer.alloc(4);
40
+ chunkSizeBuf.writeUInt32LE(CHUNK_SIZE);
41
+
42
+ this.push(Buffer.concat([MAGIC, versionBuf, chunkSizeBuf, this.salt]));
43
+ this.headerWritten = true;
44
+ }
45
+
46
+ _encryptChunk(chunk) {
47
+ const iv = crypto.randomBytes(IV_LENGTH);
48
+ const cipher = crypto.createCipheriv('aes-256-gcm', this.key, iv, { authTagLength: AUTH_TAG_LENGTH });
49
+
50
+ const encrypted = Buffer.concat([cipher.update(chunk), cipher.final()]);
51
+ const authTag = cipher.getAuthTag();
52
+
53
+ const chunkHeader = Buffer.alloc(IV_LENGTH + 4);
54
+ iv.copy(chunkHeader, 0);
55
+ chunkHeader.writeUInt32LE(encrypted.length, IV_LENGTH);
56
+
57
+ return Buffer.concat([chunkHeader, encrypted, authTag]);
58
+ }
59
+
60
+ _transform(data, encoding, callback) {
61
+ try {
62
+ this._writeHeader();
63
+
64
+ this.buffer = Buffer.concat([this.buffer, data]);
65
+
66
+ while (this.buffer.length >= CHUNK_SIZE) {
67
+ const chunk = this.buffer.subarray(0, CHUNK_SIZE);
68
+ this.buffer = this.buffer.subarray(CHUNK_SIZE);
69
+ this.push(this._encryptChunk(chunk));
70
+ }
71
+
72
+ callback();
73
+ } catch (err) {
74
+ callback(err);
75
+ }
76
+ }
77
+
78
+ _flush(callback) {
79
+ try {
80
+ this._writeHeader();
81
+
82
+ if (this.buffer.length > 0) {
83
+ this.push(this._encryptChunk(this.buffer));
84
+ }
85
+
86
+ this.key = null;
87
+ callback();
88
+ } catch (err) {
89
+ callback(err);
90
+ }
91
+ }
92
+ }
93
+
94
+ class DecryptStream extends Transform {
95
+ constructor(secret) {
96
+ super();
97
+ this.secret = secret;
98
+ this.buffer = Buffer.alloc(0);
99
+ this.headerParsed = false;
100
+ this.key = null;
101
+ this.chunkSize = CHUNK_SIZE;
102
+ }
103
+
104
+ _parseHeader() {
105
+ if (this.buffer.length < HEADER_SIZE) {
106
+ return false;
107
+ }
108
+
109
+ let offset = 0;
110
+
111
+ const magic = this.buffer.subarray(offset, offset + MAGIC.length);
112
+ if (!magic.equals(MAGIC)) {
113
+ throw new Error('Invalid encrypted file format: bad magic bytes');
114
+ }
115
+ offset += MAGIC.length;
116
+
117
+ const version = this.buffer.readUInt32LE(offset);
118
+ if (version !== VERSION) {
119
+ throw new Error(`Unsupported encryption version: ${version}`);
120
+ }
121
+ offset += 4;
122
+
123
+ this.chunkSize = this.buffer.readUInt32LE(offset);
124
+ if (this.chunkSize > MAX_CHUNK_SIZE) {
125
+ throw new Error('Invalid chunk size in header');
126
+ }
127
+ offset += 4;
128
+
129
+ this.salt = this.buffer.subarray(offset, offset + SALT_LENGTH);
130
+
131
+ this.buffer = this.buffer.subarray(HEADER_SIZE);
132
+ this.headerParsed = true;
133
+
134
+ return true;
135
+ }
136
+
137
+ _decryptChunk() {
138
+ const minChunkHeader = IV_LENGTH + 4;
139
+ if (this.buffer.length < minChunkHeader) {
140
+ return null;
141
+ }
142
+
143
+ const iv = this.buffer.subarray(0, IV_LENGTH);
144
+ const encryptedLength = this.buffer.readUInt32LE(IV_LENGTH);
145
+ if (encryptedLength > this.chunkSize + 256) {
146
+ throw new Error('Invalid encrypted chunk length');
147
+ }
148
+ const totalChunkSize = minChunkHeader + encryptedLength + AUTH_TAG_LENGTH;
149
+
150
+ if (this.buffer.length < totalChunkSize) {
151
+ return null;
152
+ }
153
+
154
+ const encryptedData = this.buffer.subarray(minChunkHeader, minChunkHeader + encryptedLength);
155
+ const authTag = this.buffer.subarray(minChunkHeader + encryptedLength, totalChunkSize);
156
+
157
+ const decipher = crypto.createDecipheriv('aes-256-gcm', this.key, iv, { authTagLength: AUTH_TAG_LENGTH });
158
+ decipher.setAuthTag(authTag);
159
+
160
+ let decrypted;
161
+ try {
162
+ decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
163
+ } catch (err) {
164
+ if (err.message.includes('auth')) {
165
+ throw new Error('Decryption failed: invalid secret or corrupted data');
166
+ }
167
+ throw err;
168
+ }
169
+
170
+ this.buffer = this.buffer.subarray(totalChunkSize);
171
+
172
+ return decrypted;
173
+ }
174
+
175
+ _processBuffer(callback) {
176
+ try {
177
+ let decrypted;
178
+ while ((decrypted = this._decryptChunk()) !== null) {
179
+ this.push(decrypted);
180
+ }
181
+ callback();
182
+ } catch (err) {
183
+ callback(err);
184
+ }
185
+ }
186
+
187
+ _transform(data, encoding, callback) {
188
+ this.buffer = Buffer.concat([this.buffer, data]);
189
+
190
+ if (!this.headerParsed) {
191
+ try {
192
+ if (!this._parseHeader()) {
193
+ callback();
194
+ return;
195
+ }
196
+ } catch (err) {
197
+ callback(err);
198
+ return;
199
+ }
200
+ crypto.scrypt(this.secret, this.salt, 32, (err, key) => {
201
+ if (err) {
202
+ callback(err);
203
+ return;
204
+ }
205
+ this.key = key;
206
+ this.secret = null;
207
+ this._processBuffer(callback);
208
+ });
209
+ return;
210
+ }
211
+
212
+ this._processBuffer(callback);
213
+ }
214
+
215
+ _flush(callback) {
216
+ try {
217
+ if (this.buffer.length > 0) {
218
+ if (!this.headerParsed) {
219
+ throw new Error('Invalid encrypted file: incomplete header');
220
+ }
221
+
222
+ const decrypted = this._decryptChunk();
223
+ if (decrypted !== null) {
224
+ this.push(decrypted);
225
+ }
226
+
227
+ if (this.buffer.length > 0) {
228
+ throw new Error('Invalid encrypted file: incomplete final chunk');
229
+ }
230
+ }
231
+
232
+ this.key = null;
233
+ callback();
234
+ } catch (err) {
235
+ callback(err);
236
+ }
237
+ }
238
+ }
239
+
240
+ async function createEncryptStream(secret) {
241
+ if (!secret) {
242
+ throw new Error('Encryption secret is required');
243
+ }
244
+ const salt = crypto.randomBytes(SALT_LENGTH);
245
+ const key = await deriveKeyAsync(secret, salt);
246
+ return new EncryptStream({ key, salt });
247
+ }
248
+
249
+ async function createDecryptStream(secret) {
250
+ if (!secret) {
251
+ throw new Error('Decryption secret is required');
252
+ }
253
+ return new DecryptStream(secret);
254
+ }
255
+
256
+ module.exports = {
257
+ createEncryptStream,
258
+ createDecryptStream,
259
+ MAGIC,
260
+ VERSION,
261
+ CHUNK_SIZE,
262
+ HEADER_SIZE
263
+ };
package/lib/tools.js CHANGED
@@ -77,6 +77,22 @@ class LRUCache extends Map {
77
77
 
78
78
  const regexCache = new LRUCache(1000);
79
79
 
80
+ function formatTokenError(provider, tokenRequest) {
81
+ let parts = [`Token request failed for ${provider}`];
82
+ if (tokenRequest) {
83
+ let detail = `${tokenRequest.grant || 'unknown'}, HTTP ${tokenRequest.status || '?'}`;
84
+ parts[0] += ` (${detail})`;
85
+ if (tokenRequest.response) {
86
+ let resp = tokenRequest.response;
87
+ let errorParts = [resp.error, resp.error_description].filter(Boolean);
88
+ if (errorParts.length) {
89
+ parts.push(errorParts.join(' - '));
90
+ }
91
+ }
92
+ }
93
+ return parts.join(': ');
94
+ }
95
+
80
96
  module.exports = {
81
97
  /**
82
98
  * Helper function to set specific bit in a buffer
@@ -1382,8 +1398,12 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEV3QUiYsp13nD9suD1/ZkEXnuMoSg
1382
1398
  },
1383
1399
 
1384
1400
  validUidValidity(value) {
1385
- if (typeof value === 'bigint' || typeof value === 'number') {
1386
- return true;
1401
+ if (typeof value === 'bigint') {
1402
+ return value > 0n;
1403
+ }
1404
+
1405
+ if (typeof value === 'number') {
1406
+ return Number.isFinite(value) && value > 0;
1387
1407
  }
1388
1408
 
1389
1409
  if (isNaN(value)) {
@@ -1762,7 +1782,10 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEV3QUiYsp13nD9suD1/ZkEXnuMoSg
1762
1782
 
1763
1783
  prepareUrl(url, baseUrl, queryParams) {
1764
1784
  let { pathname: baseUrlPathname } = new URL(baseUrl);
1765
- baseUrlPathname = baseUrlPathname.replace(/\/?/, '/');
1785
+ // Ensure pathname ends with trailing slash for proper path concatenation
1786
+ if (!baseUrlPathname.endsWith('/')) {
1787
+ baseUrlPathname += '/';
1788
+ }
1766
1789
 
1767
1790
  const urlObj = new URL(baseUrlPathname + url.replace(/^\//, ''), baseUrl);
1768
1791
 
@@ -1777,11 +1800,14 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEV3QUiYsp13nD9suD1/ZkEXnuMoSg
1777
1800
  return urlObj.href;
1778
1801
  },
1779
1802
 
1803
+ fetchAgent,
1780
1804
  retryAgent,
1781
1805
 
1782
1806
  LRUCache,
1783
1807
 
1784
- normalizeHashKeys
1808
+ normalizeHashKeys,
1809
+
1810
+ formatTokenError
1785
1811
  };
1786
1812
 
1787
1813
  function msgpackDecode(buf) {
@@ -520,6 +520,11 @@ function init(args) {
520
520
 
521
521
  const nonce = data.n || crypto.randomBytes(NONCE_BYTES).toString('base64url');
522
522
 
523
+ // Validate nonce format (base64url, 21-22 chars; also accept base64 for backward compatibility)
524
+ if (!/^[A-Za-z0-9_\-+/]{21,22}={0,2}$/.test(nonce)) {
525
+ throw Boom.badRequest('Invalid nonce format. Please generate a new authentication URL.');
526
+ }
527
+
523
528
  // store account data with atomic SET + EX
524
529
  await redis.set(`${REDIS_PREFIX}account:add:${nonce}`, JSON.stringify(accountData), 'EX', Math.floor(MAX_FORM_TTL / 1000));
525
530
 
@@ -814,11 +819,29 @@ function init(args) {
814
819
  case 'EAUTH':
815
820
  verifyResult.smtp.error = request.app.gt.gettext('Invalid username or password');
816
821
  break;
822
+ case 'ENOAUTH':
823
+ verifyResult.smtp.error = request.app.gt.gettext('Authentication credentials were not provided');
824
+ break;
825
+ case 'EOAUTH2':
826
+ verifyResult.smtp.error = request.app.gt.gettext('OAuth2 authentication failed');
827
+ break;
828
+ case 'ETLS':
829
+ verifyResult.smtp.error = request.app.gt.gettext('TLS protocol error');
830
+ break;
817
831
  case 'ESOCKET':
818
832
  if (/openssl/.test(verifyResult.smtp.error)) {
819
833
  verifyResult.smtp.error = request.app.gt.gettext('TLS protocol error');
820
834
  }
821
835
  break;
836
+ case 'ETIMEDOUT':
837
+ verifyResult.smtp.error = request.app.gt.gettext('Connection timed out');
838
+ break;
839
+ case 'ECONNECTION':
840
+ verifyResult.smtp.error = request.app.gt.gettext('Could not connect to server');
841
+ break;
842
+ case 'EPROTOCOL':
843
+ verifyResult.smtp.error = request.app.gt.gettext('Unexpected server response');
844
+ break;
822
845
  }
823
846
  }
824
847
  }
@@ -19,7 +19,8 @@ const { failAction, getByteSize, formatByteSize, getDuration, readEnvValue, hasE
19
19
 
20
20
  const { settingsSchema } = require('../schemas');
21
21
 
22
- const { DEFAULT_MAX_LOG_LINES, DEFAULT_DELIVERY_ATTEMPTS, REDIS_PREFIX, NONCE_BYTES } = consts;
22
+ const { DEFAULT_MAX_LOG_LINES, DEFAULT_DELIVERY_ATTEMPTS, REDIS_PREFIX, NONCE_BYTES, DEFAULT_GMAIL_EXPORT_BATCH_SIZE, DEFAULT_OUTLOOK_EXPORT_BATCH_SIZE } =
23
+ consts;
23
24
 
24
25
  const { fetch: fetchCmd } = require('undici');
25
26
 
@@ -359,7 +360,9 @@ function init(args) {
359
360
  enableOAuthTokensApi: (await settings.get('enableOAuthTokensApi')) || false,
360
361
  ignoreMailCertErrors: (await settings.get('ignoreMailCertErrors')) || false,
361
362
  locale: (await settings.get('locale')) || false,
362
- timezone: (await settings.get('timezone')) || false
363
+ timezone: (await settings.get('timezone')) || false,
364
+ gmailExportBatchSize: (await settings.get('gmailExportBatchSize')) || DEFAULT_GMAIL_EXPORT_BATCH_SIZE,
365
+ outlookExportBatchSize: (await settings.get('outlookExportBatchSize')) || DEFAULT_OUTLOOK_EXPORT_BATCH_SIZE
363
366
  };
364
367
 
365
368
  if (typeof values.trackClicks !== 'boolean') {
@@ -423,7 +426,9 @@ function init(args) {
423
426
  locale: request.payload.locale,
424
427
  timezone: request.payload.timezone,
425
428
  deliveryAttempts: request.payload.deliveryAttempts,
426
- imapIndexer: request.payload.imapIndexer
429
+ imapIndexer: request.payload.imapIndexer,
430
+ gmailExportBatchSize: request.payload.gmailExportBatchSize,
431
+ outlookExportBatchSize: request.payload.outlookExportBatchSize
427
432
  };
428
433
 
429
434
  if (request.payload.serviceUrl) {
@@ -530,7 +535,9 @@ function init(args) {
530
535
  .empty('')
531
536
  .valid(...locales.map(locale => locale.locale))
532
537
  .default('en'),
533
- timezone: settingsSchema.timezone.empty('')
538
+ timezone: settingsSchema.timezone.empty(''),
539
+ gmailExportBatchSize: settingsSchema.gmailExportBatchSize.default(DEFAULT_GMAIL_EXPORT_BATCH_SIZE),
540
+ outlookExportBatchSize: settingsSchema.outlookExportBatchSize.default(DEFAULT_OUTLOOK_EXPORT_BATCH_SIZE)
534
541
  })
535
542
  }
536
543
  }
@@ -2022,11 +2022,29 @@ return payload;`)
2022
2022
  case 'EAUTH':
2023
2023
  verifyResult.smtp.error = request.app.gt.gettext('Invalid username or password');
2024
2024
  break;
2025
+ case 'ENOAUTH':
2026
+ verifyResult.smtp.error = request.app.gt.gettext('Authentication credentials were not provided');
2027
+ break;
2028
+ case 'EOAUTH2':
2029
+ verifyResult.smtp.error = request.app.gt.gettext('OAuth2 authentication failed');
2030
+ break;
2031
+ case 'ETLS':
2032
+ verifyResult.smtp.error = request.app.gt.gettext('TLS protocol error');
2033
+ break;
2025
2034
  case 'ESOCKET':
2026
2035
  if (/openssl/.test(verifyResult.smtp.error)) {
2027
2036
  verifyResult.smtp.error = request.app.gt.gettext('TLS protocol error');
2028
2037
  }
2029
2038
  break;
2039
+ case 'ETIMEDOUT':
2040
+ verifyResult.smtp.error = request.app.gt.gettext('Connection timed out');
2041
+ break;
2042
+ case 'ECONNECTION':
2043
+ verifyResult.smtp.error = request.app.gt.gettext('Could not connect to server');
2044
+ break;
2045
+ case 'EPROTOCOL':
2046
+ verifyResult.smtp.error = request.app.gt.gettext('Unexpected server response');
2047
+ break;
2030
2048
  }
2031
2049
  }
2032
2050
  }
package/lib/webhooks.js CHANGED
@@ -512,9 +512,21 @@ class WebhooksHandler {
512
512
  }
513
513
 
514
514
  async pushToQueue(event, originalPayload, opts = {}) {
515
- // custom webhoom routes
515
+ // custom webhook routes
516
516
  let webhookRoutes = await this.getWebhookHandlers();
517
- let queueKeep = (await settings.get('queueKeep')) || true;
517
+ let queueKeep = (await settings.get('queueKeep')) ?? true;
518
+
519
+ let removePolicy = typeof queueKeep === 'number' ? { age: 24 * 3600, count: queueKeep } : queueKeep;
520
+ let jobOpts = {
521
+ removeOnComplete: removePolicy,
522
+ removeOnFail: removePolicy,
523
+ attempts: 10,
524
+ backoff: {
525
+ type: 'exponential',
526
+ delay: 5000,
527
+ jitter: 0.2 // 20% randomization to prevent thundering herd
528
+ }
529
+ };
518
530
 
519
531
  for (let route of webhookRoutes) {
520
532
  if (route.enabled && route.targetUrl && typeof route.filterFn === 'function') {
@@ -560,15 +572,7 @@ class WebhooksHandler {
560
572
  webhook: route.id
561
573
  });
562
574
  } else {
563
- let job = await notifyQueue.add(event, payload, {
564
- removeOnComplete: queueKeep,
565
- removeOnFail: queueKeep,
566
- attempts: 10,
567
- backoff: {
568
- type: 'exponential',
569
- delay: 5000
570
- }
571
- });
575
+ let job = await notifyQueue.add(event, payload, jobOpts);
572
576
 
573
577
  logger.trace({
574
578
  msg: 'Triggered custom webhook route',
@@ -589,15 +593,7 @@ class WebhooksHandler {
589
593
 
590
594
  if (!opts.routesOnly) {
591
595
  // MAIN webhook
592
- await notifyQueue.add(event, originalPayload, {
593
- removeOnComplete: queueKeep,
594
- removeOnFail: queueKeep,
595
- attempts: 10,
596
- backoff: {
597
- type: 'exponential',
598
- delay: 5000
599
- }
600
- });
596
+ await notifyQueue.add(event, originalPayload, jobOpts);
601
597
  }
602
598
  }
603
599
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emailengine-app",
3
- "version": "2.61.5",
3
+ "version": "2.62.0",
4
4
  "private": false,
5
5
  "productTitle": "EmailEngine",
6
6
  "description": "Email Sync Engine",
@@ -43,9 +43,9 @@
43
43
  },
44
44
  "homepage": "https://emailengine.app/",
45
45
  "dependencies": {
46
- "@bugsnag/js": "8.6.0",
47
- "@bull-board/api": "6.16.2",
48
- "@bull-board/hapi": "6.16.2",
46
+ "@bugsnag/js": "8.8.1",
47
+ "@bull-board/api": "6.16.4",
48
+ "@bull-board/hapi": "6.16.4",
49
49
  "@elastic/elasticsearch": "8.15.3",
50
50
  "@hapi/accept": "6.0.3",
51
51
  "@hapi/bell": "13.1.0",
@@ -59,16 +59,16 @@
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.2",
62
+ "@postalsys/email-ai-tools": "1.11.3",
63
63
  "@postalsys/email-text-tools": "2.4.1",
64
64
  "@postalsys/gettext": "4.1.1",
65
65
  "@postalsys/joi-messages": "1.0.5",
66
66
  "@postalsys/templates": "2.0.0",
67
67
  "@zone-eu/mailsplit": "5.4.8",
68
68
  "@zone-eu/wild-config": "1.7.3",
69
- "ace-builds": "1.43.5",
69
+ "ace-builds": "1.43.6",
70
70
  "base32.js": "0.1.0",
71
- "bullmq": "5.66.5",
71
+ "bullmq": "5.67.2",
72
72
  "compare-versions": "6.1.1",
73
73
  "dotenv": "17.2.3",
74
74
  "encoding-japanese": "2.2.0",
@@ -82,9 +82,9 @@
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.6",
85
+ "imapflow": "1.2.8",
86
86
  "ioredfour": "1.3.0-ioredis-07",
87
- "ioredis": "5.9.1",
87
+ "ioredis": "5.9.2",
88
88
  "ipaddr.js": "2.3.0",
89
89
  "joi": "17.13.3",
90
90
  "jquery": "3.7.1",
@@ -92,26 +92,26 @@
92
92
  "libmime": "5.3.7",
93
93
  "libqp": "2.1.1",
94
94
  "license-checker": "25.0.1",
95
- "mailparser": "3.9.1",
95
+ "mailparser": "3.9.3",
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": "7.0.12",
102
- "pino": "10.2.0",
101
+ "nodemailer": "8.0.0",
102
+ "pino": "10.3.0",
103
103
  "popper.js": "1.16.1",
104
104
  "prom-client": "15.1.3",
105
105
  "psl": "1.15.0",
106
- "pubface": "1.0.17",
106
+ "pubface": "1.0.18",
107
107
  "punycode.js": "2.3.1",
108
108
  "qrcode": "1.5.4",
109
- "smtp-server": "3.18.0",
109
+ "smtp-server": "3.18.1",
110
110
  "socks": "2.8.7",
111
111
  "speakeasy": "2.0.0",
112
112
  "startbootstrap-sb-admin-2": "3.3.7",
113
113
  "timezones-list": "3.1.0",
114
- "undici": "7.18.2",
114
+ "undici": "7.20.0",
115
115
  "xml2js": "0.6.2"
116
116
  },
117
117
  "devDependencies": {
@@ -125,7 +125,7 @@
125
125
  "grunt-wait": "0.3.0",
126
126
  "jsxgettext": "0.11.0",
127
127
  "pino-pretty": "13.0.0",
128
- "prettier": "3.8.0",
128
+ "prettier": "3.8.1",
129
129
  "resedit": "3.0.1",
130
130
  "spdx-satisfies": "6.0.0",
131
131
  "supertest": "7.2.2",