@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 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
- if (/[LIMIT]|rate limit/i.test(em))
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.149",
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.42",
12
+ "@bobfrankston/mailx-types": "^0.1.44",
13
13
  "@bobfrankston/mailx-settings": "^0.1.51",
14
- "@bobfrankston/mailx-store": "^0.1.83",
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.42",
40
+ "@bobfrankston/mailx-types": "^0.1.44",
41
41
  "@bobfrankston/mailx-settings": "^0.1.51",
42
- "@bobfrankston/mailx-store": "^0.1.83",
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");