emailengine-app 2.61.5 → 2.62.1
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 +88 -0
- package/data/google-crawlers.json +1 -1
- package/lib/account.js +20 -7
- package/lib/api-routes/account-routes.js +28 -5
- package/lib/api-routes/chat-routes.js +1 -1
- package/lib/api-routes/export-routes.js +316 -0
- package/lib/api-routes/message-routes.js +28 -23
- package/lib/api-routes/template-routes.js +28 -7
- package/lib/arf-detect.js +1 -1
- package/lib/autodetect-imap-settings.js +5 -5
- package/lib/consts.js +16 -0
- package/lib/db.js +3 -0
- package/lib/email-client/base-client.js +6 -4
- package/lib/email-client/gmail-client.js +205 -35
- package/lib/email-client/imap/mailbox.js +99 -8
- package/lib/email-client/imap/subconnection.js +5 -5
- package/lib/email-client/imap-client.js +76 -19
- package/lib/email-client/message-builder.js +3 -1
- package/lib/email-client/notification-handler.js +12 -9
- package/lib/email-client/outlook-client.js +364 -73
- package/lib/email-client/smtp-pool-manager.js +1 -1
- package/lib/export.js +528 -0
- package/lib/oauth/gmail.js +24 -16
- package/lib/oauth/mail-ru.js +26 -13
- package/lib/oauth/outlook.js +29 -19
- package/lib/oauth/pubsub/google.js +5 -0
- package/lib/routes-ui.js +268 -9
- package/lib/schemas.js +274 -81
- package/lib/stream-encrypt.js +263 -0
- package/lib/sub-script.js +2 -2
- package/lib/tools.js +194 -12
- package/lib/ui-routes/account-routes.js +23 -0
- package/lib/ui-routes/admin-config-routes.js +13 -6
- package/lib/ui-routes/admin-entities-routes.js +18 -0
- package/lib/webhooks.js +16 -20
- package/package.json +20 -20
- package/sbom.json +1 -1
- package/server.js +66 -7
- package/static/js/ace/ace.js +1 -1
- package/static/js/ace/ext-language_tools.js +1 -1
- package/static/licenses.html +118 -149
- package/translations/de.mo +0 -0
- package/translations/de.po +63 -36
- package/translations/en.mo +0 -0
- package/translations/en.po +64 -37
- package/translations/et.mo +0 -0
- package/translations/et.po +63 -36
- package/translations/fr.mo +0 -0
- package/translations/fr.po +63 -36
- package/translations/ja.mo +0 -0
- package/translations/ja.po +63 -36
- package/translations/messages.pot +84 -51
- package/translations/nl.mo +0 -0
- package/translations/nl.po +63 -36
- package/translations/pl.mo +0 -0
- package/translations/pl.po +63 -36
- package/views/accounts/account.hbs +375 -2
- package/views/config/network.hbs +45 -0
- package/views/config/service.hbs +35 -0
- package/workers/api.js +130 -47
- package/workers/documents.js +3 -2
- package/workers/export.js +933 -0
- package/workers/imap.js +34 -1
- package/workers/submit.js +33 -6
- package/workers/webhooks.js +20 -4
package/workers/imap.js
CHANGED
|
@@ -7,7 +7,7 @@ const logger = require('../lib/logger');
|
|
|
7
7
|
|
|
8
8
|
const { REDIS_PREFIX } = require('../lib/consts');
|
|
9
9
|
|
|
10
|
-
const { getDuration, getBoolean, emitChangeEvent, readEnvValue, hasEnvValue, threadStats } = require('../lib/tools');
|
|
10
|
+
const { getDuration, getBoolean, emitChangeEvent, readEnvValue, hasEnvValue, threadStats, reloadHttpProxyAgent } = require('../lib/tools');
|
|
11
11
|
|
|
12
12
|
const Bugsnag = require('@bugsnag/js');
|
|
13
13
|
if (readEnvValue('BUGSNAG_API_KEY')) {
|
|
@@ -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;
|
|
@@ -798,6 +826,10 @@ class ConnectionHandler {
|
|
|
798
826
|
|
|
799
827
|
switch (message.cmd) {
|
|
800
828
|
case 'settings':
|
|
829
|
+
if (message.data && ('httpProxyEnabled' in message.data || 'httpProxyUrl' in message.data)) {
|
|
830
|
+
reloadHttpProxyAgent().catch(err => logger.error({ msg: 'Failed to reload HTTP proxy agent', err }));
|
|
831
|
+
}
|
|
832
|
+
|
|
801
833
|
if (message.data && message.data.logs) {
|
|
802
834
|
for (let [account, accountObject] of this.accounts) {
|
|
803
835
|
// update log handling
|
|
@@ -841,6 +873,7 @@ class ConnectionHandler {
|
|
|
841
873
|
case 'listMessages':
|
|
842
874
|
case 'getText':
|
|
843
875
|
case 'getMessage':
|
|
876
|
+
case 'getMessages':
|
|
844
877
|
case 'updateMessage':
|
|
845
878
|
case 'updateMessages':
|
|
846
879
|
case 'listMailboxes':
|
package/workers/submit.js
CHANGED
|
@@ -7,7 +7,7 @@ const config = require('@zone-eu/wild-config');
|
|
|
7
7
|
const logger = require('../lib/logger');
|
|
8
8
|
|
|
9
9
|
const { REDIS_PREFIX } = require('../lib/consts');
|
|
10
|
-
const { getDuration, readEnvValue, threadStats } = require('../lib/tools');
|
|
10
|
+
const { getDuration, readEnvValue, threadStats, reloadHttpProxyAgent } = require('../lib/tools');
|
|
11
11
|
const { webhooks: Webhooks } = require('../lib/webhooks');
|
|
12
12
|
const settings = require('../lib/settings');
|
|
13
13
|
|
|
@@ -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
|
-
|
|
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,
|
|
@@ -474,6 +494,13 @@ parentPort.on('message', message => {
|
|
|
474
494
|
});
|
|
475
495
|
});
|
|
476
496
|
}
|
|
497
|
+
|
|
498
|
+
if (message && message.cmd === 'settings') {
|
|
499
|
+
let d = message.data || {};
|
|
500
|
+
if ('httpProxyEnabled' in d || 'httpProxyUrl' in d) {
|
|
501
|
+
reloadHttpProxyAgent().catch(err => logger.error({ msg: 'Failed to reload HTTP proxy agent', err }));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
477
504
|
});
|
|
478
505
|
|
|
479
506
|
logger.info({ msg: 'Started SMTP submission worker thread', version: packageData.version });
|
package/workers/webhooks.js
CHANGED
|
@@ -10,7 +10,7 @@ const { webhooks: Webhooks } = require('../lib/webhooks');
|
|
|
10
10
|
|
|
11
11
|
const { GooglePubSub } = require('../lib/oauth/pubsub/google');
|
|
12
12
|
|
|
13
|
-
const { readEnvValue, threadStats, getDuration,
|
|
13
|
+
const { readEnvValue, threadStats, getDuration, httpAgent, getServiceSecret, reloadHttpProxyAgent } = require('../lib/tools');
|
|
14
14
|
|
|
15
15
|
const Bugsnag = require('@bugsnag/js');
|
|
16
16
|
if (readEnvValue('BUGSNAG_API_KEY')) {
|
|
@@ -176,6 +176,13 @@ parentPort.on('message', message => {
|
|
|
176
176
|
});
|
|
177
177
|
});
|
|
178
178
|
}
|
|
179
|
+
|
|
180
|
+
if (message && message.cmd === 'settings') {
|
|
181
|
+
let d = message.data || {};
|
|
182
|
+
if ('httpProxyEnabled' in d || 'httpProxyUrl' in d) {
|
|
183
|
+
reloadHttpProxyAgent().catch(err => logger.error({ msg: 'Failed to reload HTTP proxy agent', err }));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
179
186
|
});
|
|
180
187
|
|
|
181
188
|
const notifyWorker = new Worker(
|
|
@@ -293,7 +300,7 @@ const notifyWorker = new Worker(
|
|
|
293
300
|
{
|
|
294
301
|
let filteredSubData = {};
|
|
295
302
|
let isPartial = false;
|
|
296
|
-
for (let dataKey of Object.keys(job.data.data)) {
|
|
303
|
+
for (let dataKey of Object.keys(job.data.data || {})) {
|
|
297
304
|
switch (dataKey) {
|
|
298
305
|
case 'id':
|
|
299
306
|
case 'uid':
|
|
@@ -402,7 +409,7 @@ const notifyWorker = new Worker(
|
|
|
402
409
|
method: 'post',
|
|
403
410
|
body,
|
|
404
411
|
headers,
|
|
405
|
-
dispatcher:
|
|
412
|
+
dispatcher: httpAgent.retry
|
|
406
413
|
});
|
|
407
414
|
duration = Date.now() - start;
|
|
408
415
|
} catch (err) {
|
|
@@ -546,7 +553,16 @@ route: customRoute && customRoute.id,
|
|
|
546
553
|
},
|
|
547
554
|
Object.assign(
|
|
548
555
|
{
|
|
549
|
-
concurrency: Number(NOTIFY_QC) || 1
|
|
556
|
+
concurrency: Number(NOTIFY_QC) || 1,
|
|
557
|
+
|
|
558
|
+
// Webhook HTTP requests have 90s timeout, lock should exceed this
|
|
559
|
+
lockDuration: 3 * 60 * 1000, // 3 minutes
|
|
560
|
+
|
|
561
|
+
// Check for stalled jobs every 60 seconds
|
|
562
|
+
stalledInterval: 60 * 1000,
|
|
563
|
+
|
|
564
|
+
// Allow jobs to recover from stalled state up to 3 times
|
|
565
|
+
maxStalledCount: 3
|
|
550
566
|
},
|
|
551
567
|
queueConf || {}
|
|
552
568
|
)
|