@bobfrankston/mailx-imap 0.1.149 → 0.1.151
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/index.js +15 -1
- package/package.json +5 -5
- package/test/rate-limit-backoff.test.mjs +22 -0
package/index.js
CHANGED
|
@@ -1120,8 +1120,22 @@ export class ImapManager extends EventEmitter {
|
|
|
1120
1120
|
// a rate-limit refusal stop ALL of them instead of each
|
|
1121
1121
|
// discovering it separately, once a minute, forever.
|
|
1122
1122
|
const em = e?.message || String(e);
|
|
1123
|
-
|
|
1123
|
+
// NOTE the escaping: [LIMIT] is a literal bracketed
|
|
1124
|
+
// response code. Written unescaped, [LIMIT] is a
|
|
1125
|
+
// CHARACTER CLASS matching any L/I/M/T — which is to
|
|
1126
|
+
// say almost every error message ever produced, so a
|
|
1127
|
+
// stray parse error would pause the whole account for
|
|
1128
|
+
// half an hour (shipped that way in v1.2.240, caught
|
|
1129
|
+
// 2026-08-10 when a connection-cap error tripped it).
|
|
1130
|
+
//
|
|
1131
|
+
// A connection cap belongs here too, deliberately:
|
|
1132
|
+
// Dovecot's "Maximum number of connections from
|
|
1133
|
+
// user+IP exceeded" is the same instruction as a rate
|
|
1134
|
+
// limit — stop opening sockets — and standing down for
|
|
1135
|
+
// a few minutes is what frees the slots.
|
|
1136
|
+
if (/\[LIMIT\]|rate limit|Maximum number of connections|too many connections/i.test(em)) {
|
|
1124
1137
|
this.noteRateLimited(accountId, em);
|
|
1138
|
+
}
|
|
1125
1139
|
throw e;
|
|
1126
1140
|
}
|
|
1127
1141
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-imap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.151",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
},
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
12
|
+
"@bobfrankston/mailx-types": "^0.1.44",
|
|
13
13
|
"@bobfrankston/mailx-settings": "^0.1.51",
|
|
14
|
-
"@bobfrankston/mailx-store": "^0.1.
|
|
14
|
+
"@bobfrankston/mailx-store": "^0.1.84",
|
|
15
15
|
"@bobfrankston/iflow-direct": "^0.1.65",
|
|
16
16
|
"@bobfrankston/tcp-transport": "^0.1.8",
|
|
17
17
|
"@bobfrankston/smtp-direct": "^0.1.9",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
},
|
|
38
38
|
".transformedSnapshot": {
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
40
|
+
"@bobfrankston/mailx-types": "^0.1.44",
|
|
41
41
|
"@bobfrankston/mailx-settings": "^0.1.51",
|
|
42
|
-
"@bobfrankston/mailx-store": "^0.1.
|
|
42
|
+
"@bobfrankston/mailx-store": "^0.1.84",
|
|
43
43
|
"@bobfrankston/iflow-direct": "^0.1.65",
|
|
44
44
|
"@bobfrankston/tcp-transport": "^0.1.8",
|
|
45
45
|
"@bobfrankston/smtp-direct": "^0.1.9",
|
|
@@ -59,3 +59,25 @@ for (const lane of ["sync", "quickCheck", "idle", "outbox", "fetch"]) {
|
|
|
59
59
|
assert.strictEqual(attempted, 0, "no lane may reach the connect path during a cooldown");
|
|
60
60
|
|
|
61
61
|
console.log("rate-limit backoff: 6 scenarios passed");
|
|
62
|
+
|
|
63
|
+
// 7. THE CLASSIFIER ITSELF. Shipped in v1.2.240 as /[LIMIT]|rate limit/i —
|
|
64
|
+
// an unescaped character class matching any L, I, M or T, i.e. nearly
|
|
65
|
+
// every error string in existence. A stray parse error would have paused
|
|
66
|
+
// the account for up to 30 minutes. These assertions pin the intent:
|
|
67
|
+
// back off when the SERVER says stop, never on an ordinary failure.
|
|
68
|
+
const shouldBackOff = (m) => /\[LIMIT\]|rate limit|Maximum number of connections|too many connections/i.test(m);
|
|
69
|
+
for (const m of [
|
|
70
|
+
"Login failed: [LIMIT] LOGIN Rate limit hit.",
|
|
71
|
+
"Login failed: [UNAVAILABLE] Maximum number of connections from user+IP exceeded (mail_max_userip_connections=20)",
|
|
72
|
+
"429 rate limit exceeded",
|
|
73
|
+
"too many connections",
|
|
74
|
+
]) assert.ok(shouldBackOff(m), `should back off: ${m}`);
|
|
75
|
+
for (const m of [
|
|
76
|
+
"Login failed: AUTHENTICATE failed.",
|
|
77
|
+
"inactivity timeout",
|
|
78
|
+
"SELECT INBOX failed: [NONEXISTENT] Unknown Mailbox",
|
|
79
|
+
"socket hang up",
|
|
80
|
+
"ETIMEDOUT",
|
|
81
|
+
"simpleParser threw on MIME boundary",
|
|
82
|
+
]) assert.ok(!shouldBackOff(m), `must NOT pause the account for: ${m}`);
|
|
83
|
+
console.log("rate-limit classifier: 10 messages classified correctly");
|