emailengine-app 2.69.0 → 2.70.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 (74) hide show
  1. package/.github/workflows/deploy.yml +6 -3
  2. package/.github/workflows/release.yaml +2 -0
  3. package/CHANGELOG.md +19 -0
  4. package/Gruntfile.js +3 -1
  5. package/data/google-crawlers.json +1 -1
  6. package/getswagger.sh +40 -4
  7. package/gettext-extract.js +163 -0
  8. package/lib/account.js +73 -47
  9. package/lib/api-routes/account-routes.js +231 -71
  10. package/lib/api-routes/blocklist-routes.js +25 -18
  11. package/lib/api-routes/chat-routes.js +32 -14
  12. package/lib/api-routes/delivery-test-routes.js +30 -5
  13. package/lib/api-routes/export-routes.js +27 -2
  14. package/lib/api-routes/gateway-routes.js +63 -12
  15. package/lib/api-routes/license-routes.js +18 -4
  16. package/lib/api-routes/mailbox-routes.js +33 -7
  17. package/lib/api-routes/message-routes.js +200 -58
  18. package/lib/api-routes/oauth2-app-routes.js +90 -24
  19. package/lib/api-routes/outbox-routes.js +16 -4
  20. package/lib/api-routes/pubsub-routes.js +8 -4
  21. package/lib/api-routes/route-helpers.js +14 -1
  22. package/lib/api-routes/settings-routes.js +51 -25
  23. package/lib/api-routes/stats-routes.js +37 -3
  24. package/lib/api-routes/submit-routes.js +31 -42
  25. package/lib/api-routes/template-routes.js +54 -21
  26. package/lib/api-routes/token-routes.js +67 -67
  27. package/lib/api-routes/webhook-route-routes.js +37 -8
  28. package/lib/autodetect-imap-settings.js +0 -2
  29. package/lib/consts.js +5 -0
  30. package/lib/email-client/base-client.js +28 -6
  31. package/lib/email-client/gmail-client.js +119 -112
  32. package/lib/email-client/imap/subconnection.js +0 -1
  33. package/lib/email-client/imap/sync-operations.js +1 -1
  34. package/lib/email-client/imap-client.js +36 -17
  35. package/lib/email-client/notification-handler.js +1 -4
  36. package/lib/email-client/outlook-client.js +49 -62
  37. package/lib/export.js +37 -1
  38. package/lib/feature-flags.js +2 -2
  39. package/lib/gateway.js +4 -9
  40. package/lib/get-raw-email.js +5 -5
  41. package/lib/imapproxy/imap-core/lib/imap-connection.js +0 -1
  42. package/lib/logger.js +24 -21
  43. package/lib/metrics-collector.js +0 -2
  44. package/lib/oauth2-apps.js +13 -4
  45. package/lib/outbox.js +24 -40
  46. package/lib/redis-operations.js +1 -1
  47. package/lib/schemas.js +403 -83
  48. package/lib/sentry.js +139 -0
  49. package/lib/settings.js +9 -3
  50. package/lib/stream-encrypt.js +1 -1
  51. package/lib/templates.js +1 -1
  52. package/lib/tokens.js +5 -3
  53. package/lib/tools.js +2 -4
  54. package/lib/ui-routes/account-routes.js +7 -4
  55. package/lib/ui-routes/admin-config-routes.js +16 -3
  56. package/lib/ui-routes/oauth-config-routes.js +0 -2
  57. package/lib/ui-routes/route-helpers.js +0 -2
  58. package/lib/ui-routes/unsubscribe-routes.js +0 -2
  59. package/lib/webhooks.js +8 -4
  60. package/package.json +9 -8
  61. package/sbom.json +1 -1
  62. package/server.js +8 -23
  63. package/static/licenses.html +152 -292
  64. package/translations/messages.pot +122 -122
  65. package/update-info.sh +19 -1
  66. package/views/config/logging.hbs +48 -0
  67. package/workers/api.js +11 -32
  68. package/workers/documents.js +2 -22
  69. package/workers/export.js +16 -50
  70. package/workers/imap-proxy.js +3 -23
  71. package/workers/imap.js +2 -22
  72. package/workers/smtp.js +2 -22
  73. package/workers/submit.js +6 -24
  74. package/workers/webhooks.js +2 -22
package/workers/export.js CHANGED
@@ -20,30 +20,10 @@ const {
20
20
  const { getDuration, readEnvValue, threadStats, maybeReloadHttpProxyAgent } = require('../lib/tools');
21
21
  const { webhooks: Webhooks } = require('../lib/webhooks');
22
22
  const settings = require('../lib/settings');
23
- const { Export } = require('../lib/export');
24
-
25
- const Bugsnag = require('@bugsnag/js');
26
- if (readEnvValue('BUGSNAG_API_KEY')) {
27
- Bugsnag.start({
28
- apiKey: readEnvValue('BUGSNAG_API_KEY'),
29
- appVersion: packageData.version,
30
- logger: {
31
- debug(...args) {
32
- logger.debug({ msg: args.shift(), worker: 'export', source: 'bugsnag', args: args.length ? args : undefined });
33
- },
34
- info(...args) {
35
- logger.debug({ msg: args.shift(), worker: 'export', source: 'bugsnag', args: args.length ? args : undefined });
36
- },
37
- warn(...args) {
38
- logger.warn({ msg: args.shift(), worker: 'export', source: 'bugsnag', args: args.length ? args : undefined });
39
- },
40
- error(...args) {
41
- logger.error({ msg: args.shift(), worker: 'export', source: 'bugsnag', args: args.length ? args : undefined });
42
- }
43
- }
44
- });
45
- logger.notifyError = Bugsnag.notify.bind(Bugsnag);
46
- }
23
+ const { Export, isTransientError, isSkippableError, isFolderMissingError } = require('../lib/export');
24
+
25
+ const { initSentry } = require('../lib/sentry');
26
+ initSentry('export');
47
27
 
48
28
  const { redis, queueConf } = require('../lib/db');
49
29
  const { Worker } = require('bullmq');
@@ -74,23 +54,6 @@ const IMAP_MESSAGE_RETRY_BASE_DELAY = 2000;
74
54
  const ACCOUNT_CHECK_INTERVAL = 60 * 1000;
75
55
  const LOCK_EXTENSION_INTERVAL = 5 * 60 * 1000;
76
56
 
77
- function isTransientError(err) {
78
- if (['ETIMEDOUT', 'ECONNRESET', 'ENOTFOUND', 'EAI_AGAIN', 'ECONNREFUSED', 'EPIPE', 'EHOSTUNREACH'].includes(err.code)) {
79
- return true;
80
- }
81
- if (err.statusCode >= 500 && err.statusCode < 600) {
82
- return true;
83
- }
84
- if (err.code === 'Timeout' || err.message?.includes('timeout')) {
85
- return true;
86
- }
87
- return false;
88
- }
89
-
90
- function isSkippableError(err) {
91
- return err.code === 'MessageNotFound' || err.statusCode === 404 || err.message?.includes('Failed to generate message ID');
92
- }
93
-
94
57
  let callQueue = new Map();
95
58
  let mids = 0;
96
59
 
@@ -341,7 +304,18 @@ async function indexFolder(accountObject, account, exportId, folderPath, startDa
341
304
  cursor
342
305
  };
343
306
 
344
- const result = await accountObject.listMessages(listOptions);
307
+ let result;
308
+ try {
309
+ result = await accountObject.listMessages(listOptions);
310
+ } catch (err) {
311
+ // Other errors (e.g. a deleted account) propagate
312
+ if (isFolderMissingError(err)) {
313
+ // The folder disappeared mid-export - treat it as empty instead of failing the export
314
+ logger.warn({ msg: 'Export folder was not found, skipping', account, exportId, folder: folderPath, err });
315
+ return queued;
316
+ }
317
+ throw err;
318
+ }
345
319
 
346
320
  for (const msg of result.messages || []) {
347
321
  if (maxMessages && queued >= maxMessages) {
@@ -960,11 +934,3 @@ parentPort.on('message', message => {
960
934
  });
961
935
 
962
936
  logger.info({ msg: 'Started export worker thread', version: packageData.version });
963
-
964
- module.exports = {
965
- isTransientError,
966
- isSkippableError,
967
- IMAP_MESSAGE_MAX_RETRIES,
968
- IMAP_MESSAGE_RETRY_BASE_DELAY,
969
- ACCOUNT_CHECK_INTERVAL
970
- };
@@ -5,32 +5,12 @@ const { parentPort } = require('worker_threads');
5
5
  const packageData = require('../package.json');
6
6
  const logger = require('../lib/logger');
7
7
 
8
- const { readEnvValue, threadStats } = require('../lib/tools');
8
+ const { threadStats } = require('../lib/tools');
9
9
 
10
10
  const { run } = require('../lib/imapproxy/imap-server');
11
11
 
12
- const Bugsnag = require('@bugsnag/js');
13
- if (readEnvValue('BUGSNAG_API_KEY')) {
14
- Bugsnag.start({
15
- apiKey: readEnvValue('BUGSNAG_API_KEY'),
16
- appVersion: packageData.version,
17
- logger: {
18
- debug(...args) {
19
- logger.debug({ msg: args.shift(), worker: 'imapProxy', source: 'bugsnag', args: args.length ? args : undefined });
20
- },
21
- info(...args) {
22
- logger.debug({ msg: args.shift(), worker: 'imapProxy', source: 'bugsnag', args: args.length ? args : undefined });
23
- },
24
- warn(...args) {
25
- logger.warn({ msg: args.shift(), worker: 'imapProxy', source: 'bugsnag', args: args.length ? args : undefined });
26
- },
27
- error(...args) {
28
- logger.error({ msg: args.shift(), worker: 'imapProxy', source: 'bugsnag', args: args.length ? args : undefined });
29
- }
30
- }
31
- });
32
- logger.notifyError = Bugsnag.notify.bind(Bugsnag);
33
- }
12
+ const { initSentry } = require('../lib/sentry');
13
+ initSentry('imapProxy');
34
14
 
35
15
  async function onCommand(command) {
36
16
  switch (command.cmd) {
package/workers/imap.js CHANGED
@@ -9,28 +9,8 @@ const { REDIS_PREFIX } = require('../lib/consts');
9
9
 
10
10
  const { getDuration, getBoolean, emitChangeEvent, readEnvValue, hasEnvValue, threadStats, maybeReloadHttpProxyAgent } = require('../lib/tools');
11
11
 
12
- const Bugsnag = require('@bugsnag/js');
13
- if (readEnvValue('BUGSNAG_API_KEY')) {
14
- Bugsnag.start({
15
- apiKey: readEnvValue('BUGSNAG_API_KEY'),
16
- appVersion: packageData.version,
17
- logger: {
18
- debug(...args) {
19
- logger.debug({ msg: args.shift(), worker: 'imap', source: 'bugsnag', args: args.length ? args : undefined });
20
- },
21
- info(...args) {
22
- logger.debug({ msg: args.shift(), worker: 'imap', source: 'bugsnag', args: args.length ? args : undefined });
23
- },
24
- warn(...args) {
25
- logger.warn({ msg: args.shift(), worker: 'imap', source: 'bugsnag', args: args.length ? args : undefined });
26
- },
27
- error(...args) {
28
- logger.error({ msg: args.shift(), worker: 'imap', source: 'bugsnag', args: args.length ? args : undefined });
29
- }
30
- }
31
- });
32
- logger.notifyError = Bugsnag.notify.bind(Bugsnag);
33
- }
12
+ const { initSentry } = require('../lib/sentry');
13
+ initSentry('imap');
34
14
 
35
15
  const { IMAPClient } = require('../lib/email-client/imap-client');
36
16
  const { GmailClient } = require('../lib/email-client/gmail-client');
package/workers/smtp.js CHANGED
@@ -9,28 +9,8 @@ const logger = require('../lib/logger');
9
9
  const { getDuration, emitChangeEvent, readEnvValue, threadStats, loadTlsConfig, getByteSize } = require('../lib/tools');
10
10
  const { matchIp } = require('../lib/utils/network');
11
11
 
12
- const Bugsnag = require('@bugsnag/js');
13
- if (readEnvValue('BUGSNAG_API_KEY')) {
14
- Bugsnag.start({
15
- apiKey: readEnvValue('BUGSNAG_API_KEY'),
16
- appVersion: packageData.version,
17
- logger: {
18
- debug(...args) {
19
- logger.debug({ msg: args.shift(), worker: 'smtp', source: 'bugsnag', args: args.length ? args : undefined });
20
- },
21
- info(...args) {
22
- logger.debug({ msg: args.shift(), worker: 'smtp', source: 'bugsnag', args: args.length ? args : undefined });
23
- },
24
- warn(...args) {
25
- logger.warn({ msg: args.shift(), worker: 'smtp', source: 'bugsnag', args: args.length ? args : undefined });
26
- },
27
- error(...args) {
28
- logger.error({ msg: args.shift(), worker: 'smtp', source: 'bugsnag', args: args.length ? args : undefined });
29
- }
30
- }
31
- });
32
- logger.notifyError = Bugsnag.notify.bind(Bugsnag);
33
- }
12
+ const { initSentry } = require('../lib/sentry');
13
+ initSentry('smtp');
34
14
 
35
15
  const { SMTPServer } = require('smtp-server');
36
16
  const util = require('util');
package/workers/submit.js CHANGED
@@ -11,28 +11,8 @@ const { getDuration, readEnvValue, threadStats, maybeReloadHttpProxyAgent } = re
11
11
  const { webhooks: Webhooks } = require('../lib/webhooks');
12
12
  const settings = require('../lib/settings');
13
13
 
14
- const Bugsnag = require('@bugsnag/js');
15
- if (readEnvValue('BUGSNAG_API_KEY')) {
16
- Bugsnag.start({
17
- apiKey: readEnvValue('BUGSNAG_API_KEY'),
18
- appVersion: packageData.version,
19
- logger: {
20
- debug(...args) {
21
- logger.debug({ msg: args.shift(), worker: 'submit', source: 'bugsnag', args: args.length ? args : undefined });
22
- },
23
- info(...args) {
24
- logger.debug({ msg: args.shift(), worker: 'submit', source: 'bugsnag', args: args.length ? args : undefined });
25
- },
26
- warn(...args) {
27
- logger.warn({ msg: args.shift(), worker: 'submit', source: 'bugsnag', args: args.length ? args : undefined });
28
- },
29
- error(...args) {
30
- logger.error({ msg: args.shift(), worker: 'submit', source: 'bugsnag', args: args.length ? args : undefined });
31
- }
32
- }
33
- });
34
- logger.notifyError = Bugsnag.notify.bind(Bugsnag);
35
- }
14
+ const { initSentry } = require('../lib/sentry');
15
+ initSentry('submit');
36
16
 
37
17
  const util = require('util');
38
18
  const { redis, queueConf, submitQueue } = require('../lib/db');
@@ -222,13 +202,15 @@ const submitWorker = new Worker(
222
202
  }
223
203
 
224
204
  let backoffDelay = Number(job.opts.backoff && job.opts.backoff.delay) || 0;
225
- let nextAttempt = job.attemptsMade < job.opts.attempts ? Math.round(job.processedOn + Math.pow(2, job.attemptsMade) * backoffDelay) : false;
205
+ // job.attemptsMade is not yet incremented for the ongoing attempt, so the
206
+ // next retry (if this attempt fails) is delayed by 2^attemptsMade * base
207
+ let nextAttempt = job.attemptsMade + 1 < job.opts.attempts ? Math.round(job.processedOn + Math.pow(2, job.attemptsMade) * backoffDelay) : false;
226
208
 
227
209
  queueEntry.job = {
228
210
  id: job.id,
229
211
  attemptsMade: job.attemptsMade,
230
212
  attempts: job.opts.attempts,
231
- nextAttempt: new Date(nextAttempt).toISOString()
213
+ nextAttempt: nextAttempt ? new Date(nextAttempt).toISOString() : false
232
214
  };
233
215
 
234
216
  let res = await accountObject.submitMessage(queueEntry);
@@ -13,28 +13,8 @@ const { GooglePubSub } = require('../lib/oauth/pubsub/google');
13
13
  const { readEnvValue, threadStats, getDuration, httpAgent, getServiceSecret, maybeReloadHttpProxyAgent } = require('../lib/tools');
14
14
  const { sendWebhookRequest } = require('../lib/webhook-request');
15
15
 
16
- const Bugsnag = require('@bugsnag/js');
17
- if (readEnvValue('BUGSNAG_API_KEY')) {
18
- Bugsnag.start({
19
- apiKey: readEnvValue('BUGSNAG_API_KEY'),
20
- appVersion: packageData.version,
21
- logger: {
22
- debug(...args) {
23
- logger.debug({ msg: args.shift(), worker: 'webhooks', source: 'bugsnag', args: args.length ? args : undefined });
24
- },
25
- info(...args) {
26
- logger.debug({ msg: args.shift(), worker: 'webhooks', source: 'bugsnag', args: args.length ? args : undefined });
27
- },
28
- warn(...args) {
29
- logger.warn({ msg: args.shift(), worker: 'webhooks', source: 'bugsnag', args: args.length ? args : undefined });
30
- },
31
- error(...args) {
32
- logger.error({ msg: args.shift(), worker: 'webhooks', source: 'bugsnag', args: args.length ? args : undefined });
33
- }
34
- }
35
- });
36
- logger.notifyError = Bugsnag.notify.bind(Bugsnag);
37
- }
16
+ const { initSentry } = require('../lib/sentry');
17
+ initSentry('webhooks');
38
18
 
39
19
  const { redis, queueConf } = require('../lib/db');
40
20
  const { Worker } = require('bullmq');