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
package/workers/imap.js CHANGED
@@ -452,6 +452,34 @@ class ConnectionHandler {
452
452
  return await accountData.connection.getMessage(message.message, message.options);
453
453
  }
454
454
 
455
+ async getMessages(message) {
456
+ if (!this.accounts.has(message.account)) {
457
+ throw NO_ACTIVE_HANDLER_RESP_ERR;
458
+ }
459
+
460
+ let accountData = this.accounts.get(message.account);
461
+ if (!accountData.connection) {
462
+ throw NO_ACTIVE_HANDLER_RESP_ERR;
463
+ }
464
+
465
+ // Use batch method if available (Gmail/Outlook API clients)
466
+ if (typeof accountData.connection.getMessages === 'function') {
467
+ return await accountData.connection.getMessages(message.messageIds, message.options);
468
+ }
469
+
470
+ // Fallback to sequential fetching for IMAP
471
+ const results = [];
472
+ for (const messageId of message.messageIds) {
473
+ try {
474
+ const msg = await accountData.connection.getMessage(messageId, message.options);
475
+ results.push({ messageId, data: msg, error: null });
476
+ } catch (err) {
477
+ results.push({ messageId, data: null, error: { message: err.message, code: err.code } });
478
+ }
479
+ }
480
+ return results;
481
+ }
482
+
455
483
  async updateMessage(message) {
456
484
  if (!this.accounts.has(message.account)) {
457
485
  throw NO_ACTIVE_HANDLER_RESP_ERR;
@@ -841,6 +869,7 @@ class ConnectionHandler {
841
869
  case 'listMessages':
842
870
  case 'getText':
843
871
  case 'getMessage':
872
+ case 'getMessages':
844
873
  case 'updateMessage':
845
874
  case 'updateMessages':
846
875
  case 'listMailboxes':
package/workers/submit.js CHANGED
@@ -62,6 +62,16 @@ const SUBMIT_QC = (readEnvValue('EENGINE_SUBMIT_QC') && Number(readEnvValue('EEN
62
62
 
63
63
  const SUBMIT_DELAY = getDuration(readEnvValue('EENGINE_SUBMIT_DELAY') || config.submitDelay) || null;
64
64
 
65
+ const NON_RETRYABLE_CODES = new Set([
66
+ 'EAUTH', // authentication failed
67
+ 'ENOAUTH', // no credentials provided
68
+ 'EOAUTH2', // OAuth2 token failure
69
+ 'ETLS', // TLS handshake failed
70
+ 'EENVELOPE', // invalid sender/recipients
71
+ 'EMESSAGE', // message content error
72
+ 'EPROTOCOL' // SMTP protocol mismatch
73
+ ]);
74
+
65
75
  let callQueue = new Map();
66
76
  let mids = 0;
67
77
 
@@ -295,9 +305,11 @@ const submitWorker = new Worker(
295
305
  // ignore
296
306
  }
297
307
 
298
- if (err.statusCode >= 500 && job.attemptsMade < job.opts.attempts) {
308
+ const isPermanentSmtp = err.statusCode >= 500 && err.statusCode !== 503;
309
+ const isPermanentCode = NON_RETRYABLE_CODES.has(err.code);
310
+ if ((isPermanentSmtp || isPermanentCode) && job.attemptsMade < job.opts.attempts) {
299
311
  try {
300
- // do not retry after 5xx error
312
+ // do not retry after 5xx error (except 503 which is transient)
301
313
  await job.discard();
302
314
  logger.info({
303
315
  msg: 'Job discarded',
@@ -305,9 +317,6 @@ const submitWorker = new Worker(
305
317
  queueId: job.data.queueId
306
318
  });
307
319
  } catch (E) {
308
- // ignore
309
- logger.error({ msg: 'Failed to discard job', account: queueEntry.account, queueId: job.data.queueId, err: E });
310
-
311
320
  logger.error({
312
321
  msg: 'Failed to discard job',
313
322
  action: 'submit',
@@ -329,6 +338,17 @@ const submitWorker = new Worker(
329
338
  {
330
339
  concurrency: SUBMIT_QC,
331
340
 
341
+ // Lock duration must exceed SMTP socket timeout (2 min) to prevent
342
+ // jobs from being marked stalled during normal email delivery
343
+ lockDuration: 3 * 60 * 1000, // 3 minutes
344
+
345
+ // Check for stalled jobs every 60 seconds
346
+ stalledInterval: 60 * 1000,
347
+
348
+ // Allow jobs to recover from stalled state up to 3 times before failing
349
+ // This handles transient Redis latency or connection issues
350
+ maxStalledCount: 3,
351
+
332
352
  limiter: SUBMIT_DELAY
333
353
  ? {
334
354
  max: 1,
@@ -293,7 +293,7 @@ const notifyWorker = new Worker(
293
293
  {
294
294
  let filteredSubData = {};
295
295
  let isPartial = false;
296
- for (let dataKey of Object.keys(job.data.data)) {
296
+ for (let dataKey of Object.keys(job.data.data || {})) {
297
297
  switch (dataKey) {
298
298
  case 'id':
299
299
  case 'uid':
@@ -546,7 +546,16 @@ route: customRoute && customRoute.id,
546
546
  },
547
547
  Object.assign(
548
548
  {
549
- concurrency: Number(NOTIFY_QC) || 1
549
+ concurrency: Number(NOTIFY_QC) || 1,
550
+
551
+ // Webhook HTTP requests have 90s timeout, lock should exceed this
552
+ lockDuration: 3 * 60 * 1000, // 3 minutes
553
+
554
+ // Check for stalled jobs every 60 seconds
555
+ stalledInterval: 60 * 1000,
556
+
557
+ // Allow jobs to recover from stalled state up to 3 times
558
+ maxStalledCount: 3
550
559
  },
551
560
  queueConf || {}
552
561
  )