emailengine-app 2.68.1 → 2.69.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.
- package/.github/workflows/deploy.yml +2 -0
- package/.github/workflows/release.yaml +4 -0
- package/CHANGELOG.md +40 -0
- package/config/default.toml +2 -0
- package/data/google-crawlers.json +7 -1
- package/lib/account.js +62 -25
- package/lib/api-routes/account-routes.js +493 -75
- package/lib/api-routes/blocklist-routes.js +337 -0
- package/lib/api-routes/delivery-test-routes.js +321 -0
- package/lib/api-routes/export-routes.js +1 -12
- package/lib/api-routes/gateway-routes.js +376 -0
- package/lib/api-routes/license-routes.js +142 -0
- package/lib/api-routes/mailbox-routes.js +318 -0
- package/lib/api-routes/message-routes.js +21 -129
- package/lib/api-routes/oauth2-app-routes.js +631 -0
- package/lib/api-routes/outbox-routes.js +173 -0
- package/lib/api-routes/pubsub-routes.js +98 -0
- package/lib/api-routes/route-helpers.js +45 -0
- package/lib/api-routes/settings-routes.js +331 -0
- package/lib/api-routes/stats-routes.js +77 -0
- package/lib/api-routes/submit-routes.js +472 -0
- package/lib/api-routes/template-routes.js +7 -55
- package/lib/api-routes/token-routes.js +297 -0
- package/lib/api-routes/webhook-route-routes.js +152 -0
- package/lib/email-client/gmail-client.js +14 -0
- package/lib/email-client/imap/mailbox.js +34 -11
- package/lib/email-client/imap/subconnection.js +20 -12
- package/lib/email-client/imap/sync-operations.js +130 -2
- package/lib/email-client/imap-client.js +116 -58
- package/lib/email-client/outlook-client.js +85 -13
- package/lib/export.js +60 -19
- package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
- package/lib/imapproxy/imap-core/lib/imap-command.js +6 -1
- package/lib/imapproxy/imap-core/lib/imap-connection.js +106 -23
- package/lib/imapproxy/imap-core/lib/imap-server.js +24 -0
- package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
- package/lib/message-port-stream.js +113 -16
- package/lib/reject-worker-calls.js +42 -0
- package/lib/routes-ui.js +37 -8778
- package/lib/schemas.js +26 -1
- package/lib/tools.js +68 -0
- package/lib/ui-routes/account-routes.js +40 -210
- package/lib/ui-routes/admin-config-routes.js +913 -487
- package/lib/ui-routes/admin-entities-routes.js +1 -0
- package/lib/ui-routes/auth-routes.js +1339 -0
- package/lib/ui-routes/dashboard-routes.js +188 -0
- package/lib/ui-routes/document-store-routes.js +800 -0
- package/lib/ui-routes/export-routes.js +217 -0
- package/lib/ui-routes/internals-routes.js +354 -0
- package/lib/ui-routes/network-config-routes.js +759 -0
- package/lib/ui-routes/{oauth-routes.js → oauth-config-routes.js} +371 -91
- package/lib/ui-routes/route-helpers.js +316 -0
- package/lib/ui-routes/smtp-test-routes.js +236 -0
- package/lib/ui-routes/unsubscribe-routes.js +234 -0
- package/lib/webhook-request.js +36 -0
- package/package.json +8 -8
- package/sbom.json +1 -1
- package/server.js +214 -16
- package/static/licenses.html +12 -12
- package/translations/messages.pot +129 -149
- package/views/dashboard.hbs +7 -26
- package/views/internals/index.hbs +15 -0
- package/views/tokens/index.hbs +9 -0
- package/workers/api.js +198 -4401
- package/workers/export.js +87 -54
- package/workers/imap.js +29 -13
- package/workers/submit.js +20 -11
- package/workers/webhooks.js +6 -20
|
@@ -16,6 +16,7 @@ jobs:
|
|
|
16
16
|
deploy:
|
|
17
17
|
name: Deploy Demo App
|
|
18
18
|
runs-on: ubuntu-24.04
|
|
19
|
+
timeout-minutes: 15
|
|
19
20
|
|
|
20
21
|
steps:
|
|
21
22
|
- name: Checkout
|
|
@@ -63,6 +64,7 @@ jobs:
|
|
|
63
64
|
docker:
|
|
64
65
|
name: Build Docker Image
|
|
65
66
|
runs-on: ubuntu-24.04
|
|
67
|
+
timeout-minutes: 30
|
|
66
68
|
permissions:
|
|
67
69
|
contents: read
|
|
68
70
|
packages: write
|
|
@@ -63,6 +63,10 @@ jobs:
|
|
|
63
63
|
runs-on: ubuntu-latest
|
|
64
64
|
needs: release_please
|
|
65
65
|
if: ${{needs.release_please.outputs.release_created}}
|
|
66
|
+
# Multi-arch builds run the arm64 leg under QEMU emulation, which can
|
|
67
|
+
# hang indefinitely on a misbehaving step. A normal build finishes in
|
|
68
|
+
# ~4 minutes, so fail fast instead of burning the 6-hour default limit.
|
|
69
|
+
timeout-minutes: 30
|
|
66
70
|
steps:
|
|
67
71
|
- run: echo version v${{needs.release_please.outputs.major}}.${{needs.release_please.outputs.minor}}.${{needs.release_please.outputs.patch}}
|
|
68
72
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.69.0](https://github.com/postalsys/emailengine/compare/v2.68.1...v2.69.0) (2026-06-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add label/category filtering to message search ([c171c4e](https://github.com/postalsys/emailengine/commit/c171c4e1efe582f7e532b69108110a1c48aa3ede))
|
|
9
|
+
* detect unsafe Redis eviction config in dashboard warnings ([712d5d6](https://github.com/postalsys/emailengine/commit/712d5d69b99cc45c6f3e123a084ef054236e0110))
|
|
10
|
+
* rebuild lost IMAP sync state without replaying messageNew ([9fe391e](https://github.com/postalsys/emailengine/commit/9fe391e9d6350485aded780709305d698a4c65fb))
|
|
11
|
+
* surface token hash for identification in API, UI, and logs ([8e2a5a9](https://github.com/postalsys/emailengine/commit/8e2a5a9f2bb5194e92575c34c0006a8c4e21888d))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* avoid 500 on GET /admin/totp without a partial-auth session ([b6f2394](https://github.com/postalsys/emailengine/commit/b6f2394182379d94a05f95c84f5ce04ae6f87a05))
|
|
17
|
+
* bound inbound line length in the IMAP proxy parser ([9fb8a5e](https://github.com/postalsys/emailengine/commit/9fb8a5e301564dee46b48ca32a5b84e4832d96e8))
|
|
18
|
+
* close leaks and a worker crash found in the connection hardening review ([e97950e](https://github.com/postalsys/emailengine/commit/e97950e7e907ac5b5549302b8865c5d2bfb0d6e2))
|
|
19
|
+
* count undecodable export queue entries as skipped ([72e2498](https://github.com/postalsys/emailengine/commit/72e2498390f17ad24d5f7b1577540674d7e1fe09))
|
|
20
|
+
* distinguish SO_REUSEPORT fallback cause in logs and admin UI ([99ad9f3](https://github.com/postalsys/emailengine/commit/99ad9f3359eab5bafc4726535584874d4823f7bd))
|
|
21
|
+
* drain webhook response body on every delivery path ([2cc3e7a](https://github.com/postalsys/emailengine/commit/2cc3e7aef114b5b913dea5c363278e1a883d5a48))
|
|
22
|
+
* extract gettext strings from the decomposed ui-routes modules ([227e60f](https://github.com/postalsys/emailengine/commit/227e60ff4f354c5d0f55534877c8ab6fbdd92c7d))
|
|
23
|
+
* guard cross-thread download streams against early producer errors ([191ec62](https://github.com/postalsys/emailengine/commit/191ec6204887c3abe78da7a9944e3165b19c0b87))
|
|
24
|
+
* guard IMAP notification handlers against malformed events ([0248953](https://github.com/postalsys/emailengine/commit/0248953a5dcccba5d46704bf7113a70fb5ac3150))
|
|
25
|
+
* guard webhook notifications in BullMQ failure handlers ([e90048e](https://github.com/postalsys/emailengine/commit/e90048e7b7bd92916219602f92305342234df4a3))
|
|
26
|
+
* harden IMAP proxy STARTTLS against command injection ([3661ddd](https://github.com/postalsys/emailengine/commit/3661dddbf216b29ec8f074d3f6422dd4db5d9e7b))
|
|
27
|
+
* harden inter-thread IPC (worker-death call rejection, webhook guards, stream cleanup) ([0111a8e](https://github.com/postalsys/emailengine/commit/0111a8e2bf139c3412cc37e5e933fd5316cee8fc))
|
|
28
|
+
* harden message export against data loss and corruption ([540d867](https://github.com/postalsys/emailengine/commit/540d8678ec25f62e8123fc78fa929f4e4713b5d4))
|
|
29
|
+
* harden multiple-API-worker startup and centralize proxy-agent reload ([7f2db9e](https://github.com/postalsys/emailengine/commit/7f2db9e557d960aa1b7523b7d8678598683c2f29))
|
|
30
|
+
* list Outlook folders with slashes in the name ([dd35e7b](https://github.com/postalsys/emailengine/commit/dd35e7b262a47f9395811d75c4bcc1d9ab4c6ec0))
|
|
31
|
+
* only reseed lost IMAP sync state when no stored state exists ([78e0141](https://github.com/postalsys/emailengine/commit/78e0141e9f3478d2a2a1206c805258c6807a45d8))
|
|
32
|
+
* prevent IMAP worker crash on download stream errors ([d9d5396](https://github.com/postalsys/emailengine/commit/d9d539632cc245a8e2a14585fc7918186bee00b6))
|
|
33
|
+
* prevent post-BYE command dispatch in IMAP proxy teardown ([d51e92d](https://github.com/postalsys/emailengine/commit/d51e92d298e9373a6dc5c2eb0919885e161b45c7))
|
|
34
|
+
* re-arm a generous idle timeout for proxied IMAP connections ([627c6d4](https://github.com/postalsys/emailengine/commit/627c6d472e50f6fc3e958e4774226557a526018d))
|
|
35
|
+
* reject in-flight worker calls when a worker thread terminates ([08e7b01](https://github.com/postalsys/emailengine/commit/08e7b0128a4b8e9f9e5a96fefe49110bd032bd2c))
|
|
36
|
+
* release cross-thread download streams on abort and error ([6514249](https://github.com/postalsys/emailengine/commit/65142499d095bbcb5f0bb49755a2e24f6e52fa62))
|
|
37
|
+
* release MessagePort streams when message/attachment downloads fail ([787f4f5](https://github.com/postalsys/emailengine/commit/787f4f5ef6364ec1389c15269c8f7f8eaaa2142a))
|
|
38
|
+
* return 422 for unsupported label search and surface Outlook categories ([d981359](https://github.com/postalsys/emailengine/commit/d981359b78b61d2c8a03e4e35086a4c9674ce53a))
|
|
39
|
+
* return the append destination folder from uploadMessage ([96aed6b](https://github.com/postalsys/emailengine/commit/96aed6b1144e1d3ee9da79b3a1fb03606245f83c))
|
|
40
|
+
* send correctly typed SMTP and IMAP proxy state change notifications ([3fbd27f](https://github.com/postalsys/emailengine/commit/3fbd27fc08bb74f9738a3fc95d4177fa4978c263))
|
|
41
|
+
* tear down subconnections when deleting an IMAP account ([bd81a33](https://github.com/postalsys/emailengine/commit/bd81a33caf9400cbee2781a9180f1a901c1d6e48))
|
|
42
|
+
|
|
3
43
|
## [2.68.1](https://github.com/postalsys/emailengine/compare/v2.68.0...v2.68.1) (2026-06-01)
|
|
4
44
|
|
|
5
45
|
|
package/config/default.toml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"creationTime": "2026-
|
|
2
|
+
"creationTime": "2026-06-09T14:45:50.000000",
|
|
3
3
|
"prefixes": [
|
|
4
4
|
{
|
|
5
5
|
"ipv6Prefix": "2001:4860:4801:2008::/64"
|
|
@@ -310,6 +310,9 @@
|
|
|
310
310
|
{
|
|
311
311
|
"ipv6Prefix": "2001:4860:4801:207e::/64"
|
|
312
312
|
},
|
|
313
|
+
{
|
|
314
|
+
"ipv6Prefix": "2001:4860:4801:207f::/64"
|
|
315
|
+
},
|
|
313
316
|
{
|
|
314
317
|
"ipv6Prefix": "2001:4860:4801:2080::/64"
|
|
315
318
|
},
|
|
@@ -796,6 +799,9 @@
|
|
|
796
799
|
{
|
|
797
800
|
"ipv4Prefix": "74.125.219.192/27"
|
|
798
801
|
},
|
|
802
|
+
{
|
|
803
|
+
"ipv4Prefix": "74.125.219.224/27"
|
|
804
|
+
},
|
|
799
805
|
{
|
|
800
806
|
"ipv4Prefix": "74.125.219.32/27"
|
|
801
807
|
},
|
package/lib/account.js
CHANGED
|
@@ -1084,22 +1084,45 @@ class Account {
|
|
|
1084
1084
|
};
|
|
1085
1085
|
}
|
|
1086
1086
|
|
|
1087
|
+
// Creates the consumer side of a cross-thread download stream, with a guard listener:
|
|
1088
|
+
// the producer can post {error} before Hapi attaches its own 'error' listeners (or
|
|
1089
|
+
// while the setup call is still pending). Without a listener that emission is an
|
|
1090
|
+
// uncaught exception that kills the API worker.
|
|
1091
|
+
createDownloadStream(failureLogMessage) {
|
|
1092
|
+
const { port1, port2 } = new MessageChannel();
|
|
1093
|
+
const stream = new MessagePortReadable(port1);
|
|
1094
|
+
|
|
1095
|
+
stream.on('error', err => {
|
|
1096
|
+
this.logger.error({ msg: failureLogMessage, account: this.account, err });
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
return { stream, port2 };
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1087
1102
|
async getRawMessage(message) {
|
|
1088
1103
|
await this.loadAccountData(this.account, true);
|
|
1089
1104
|
|
|
1090
|
-
const {
|
|
1091
|
-
const stream = new MessagePortReadable(port1);
|
|
1105
|
+
const { stream, port2 } = this.createDownloadStream('Message source stream failed');
|
|
1092
1106
|
|
|
1093
|
-
let streamCreated
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1107
|
+
let streamCreated;
|
|
1108
|
+
try {
|
|
1109
|
+
streamCreated = await this.call(
|
|
1110
|
+
{
|
|
1111
|
+
cmd: 'getRawMessage',
|
|
1112
|
+
account: this.account,
|
|
1113
|
+
message,
|
|
1114
|
+
timeout: this.timeout,
|
|
1115
|
+
port: port2
|
|
1116
|
+
},
|
|
1117
|
+
[port2]
|
|
1118
|
+
);
|
|
1119
|
+
} catch (err) {
|
|
1120
|
+
// The setup call failed (timeout, worker gone, 404). Destroy the reader so
|
|
1121
|
+
// its MessagePort + listener are released instead of leaking, and so any
|
|
1122
|
+
// late producer data is not buffered forever.
|
|
1123
|
+
stream.destroy();
|
|
1124
|
+
throw err;
|
|
1125
|
+
}
|
|
1103
1126
|
|
|
1104
1127
|
if (streamCreated && streamCreated.headers) {
|
|
1105
1128
|
stream.headers = streamCreated.headers;
|
|
@@ -1111,19 +1134,27 @@ class Account {
|
|
|
1111
1134
|
async getAttachment(attachment) {
|
|
1112
1135
|
await this.loadAccountData(this.account, true);
|
|
1113
1136
|
|
|
1114
|
-
const {
|
|
1115
|
-
const stream = new MessagePortReadable(port1);
|
|
1137
|
+
const { stream, port2 } = this.createDownloadStream('Attachment stream failed');
|
|
1116
1138
|
|
|
1117
|
-
let streamCreated
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1139
|
+
let streamCreated;
|
|
1140
|
+
try {
|
|
1141
|
+
streamCreated = await this.call(
|
|
1142
|
+
{
|
|
1143
|
+
cmd: 'getAttachment',
|
|
1144
|
+
account: this.account,
|
|
1145
|
+
attachment,
|
|
1146
|
+
timeout: this.timeout,
|
|
1147
|
+
port: port2
|
|
1148
|
+
},
|
|
1149
|
+
[port2]
|
|
1150
|
+
);
|
|
1151
|
+
} catch (err) {
|
|
1152
|
+
// The setup call failed (timeout, worker gone, 404). Destroy the reader so
|
|
1153
|
+
// its MessagePort + listener are released instead of leaking, and so any
|
|
1154
|
+
// late producer data is not buffered forever.
|
|
1155
|
+
stream.destroy();
|
|
1156
|
+
throw err;
|
|
1157
|
+
}
|
|
1127
1158
|
|
|
1128
1159
|
if (streamCreated && streamCreated.headers) {
|
|
1129
1160
|
stream.headers = streamCreated.headers;
|
|
@@ -1547,14 +1578,20 @@ class Account {
|
|
|
1547
1578
|
|
|
1548
1579
|
// download large inline attachments not stored in ES
|
|
1549
1580
|
for (let attachment of attachmentsToDownload) {
|
|
1581
|
+
let downloadStream;
|
|
1550
1582
|
try {
|
|
1551
|
-
|
|
1583
|
+
downloadStream = await this.getAttachment(attachment.id);
|
|
1552
1584
|
if (downloadStream) {
|
|
1553
1585
|
let content = await download(downloadStream);
|
|
1554
1586
|
this.logger.trace({ msg: 'Fetched attachment content', account: this.account, attachment, size: content.length });
|
|
1555
1587
|
attachment.content = content.toString('base64');
|
|
1556
1588
|
}
|
|
1557
1589
|
} catch (err) {
|
|
1590
|
+
// Release the reader if download() failed mid-stream so its
|
|
1591
|
+
// MessagePort is not left open (destroy() is idempotent).
|
|
1592
|
+
if (downloadStream) {
|
|
1593
|
+
downloadStream.destroy();
|
|
1594
|
+
}
|
|
1558
1595
|
this.logger.error({ msg: 'Failed to fetch attachment content', account: this.account, attachment, err });
|
|
1559
1596
|
}
|
|
1560
1597
|
}
|