@bobfrankston/rmfmail 1.2.243 → 1.2.245

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.
@@ -1117,7 +1117,22 @@ export class ImapManager extends EventEmitter {
1117
1117
  // a rate-limit refusal stop ALL of them instead of each
1118
1118
  // discovering it separately, once a minute, forever.
1119
1119
  const em = e?.message || String(e);
1120
- if (/[LIMIT]|rate limit/i.test(em)) this.noteRateLimited(accountId, em);
1120
+ // NOTE the escaping: [LIMIT] is a literal bracketed
1121
+ // response code. Written unescaped, [LIMIT] is a
1122
+ // CHARACTER CLASS matching any L/I/M/T — which is to
1123
+ // say almost every error message ever produced, so a
1124
+ // stray parse error would pause the whole account for
1125
+ // half an hour (shipped that way in v1.2.240, caught
1126
+ // 2026-08-10 when a connection-cap error tripped it).
1127
+ //
1128
+ // A connection cap belongs here too, deliberately:
1129
+ // Dovecot's "Maximum number of connections from
1130
+ // user+IP exceeded" is the same instruction as a rate
1131
+ // limit — stop opening sockets — and standing down for
1132
+ // a few minutes is what frees the slots.
1133
+ if (/\[LIMIT\]|rate limit|Maximum number of connections|too many connections/i.test(em)) {
1134
+ this.noteRateLimited(accountId, em);
1135
+ }
1121
1136
  throw e;
1122
1137
  }
1123
1138
  };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.149",
3
+ "version": "0.1.150",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bobfrankston/mailx-imap",
9
- "version": "0.1.149",
9
+ "version": "0.1.150",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@bobfrankston/iflow-direct": "^0.1.27",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.149",
3
+ "version": "0.1.150",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -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");