@bobfrankston/mailx-imap 0.1.85 → 0.1.86

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.
Files changed (2) hide show
  1. package/index.js +34 -8
  2. package/package.json +11 -11
package/index.js CHANGED
@@ -4193,19 +4193,37 @@ export class ImapManager extends EventEmitter {
4193
4193
  if (host !== this.hostname)
4194
4194
  continue;
4195
4195
  const pid = parseInt(pidStr);
4196
- if (pid === myPid)
4197
- continue; // it's us
4196
+ let ageMs = Infinity;
4197
+ try {
4198
+ ageMs = Date.now() - fs.statSync(path.join(dir, f)).mtimeMs;
4199
+ }
4200
+ catch { /* */ }
4201
+ if (pid === myPid) {
4202
+ // Our own claim. Normally we're actively sending it — leave
4203
+ // it. But the send is now bounded (60s APPEND timeout), so
4204
+ // an OWN claim older than STALE_CLAIM_MS means the send
4205
+ // hung past its timeout, or a prior tick orphaned it (the
4206
+ // release rename failed). Without reclaiming it the file
4207
+ // sits in `.sending-` forever — recovery used to skip every
4208
+ // own-PID claim unconditionally, so a transient connection
4209
+ // wedge pinned the message even after the link recovered
4210
+ // (Bob 2026-06-11: two messages stuck .sending-<ourpid>
4211
+ // while SELECT Outbox was already succeeding again).
4212
+ if (ageMs < STALE_CLAIM_MS)
4213
+ continue;
4214
+ try {
4215
+ fs.renameSync(path.join(dir, f), path.join(dir, original));
4216
+ console.log(` [outbox] Recovered our own stale claim ${f} → ${original} (hung ${Math.round(ageMs / 60_000)}m)`);
4217
+ }
4218
+ catch { /* ignore */ }
4219
+ continue;
4220
+ }
4198
4221
  let alive = false;
4199
4222
  try {
4200
4223
  process.kill(pid, 0);
4201
4224
  alive = true;
4202
4225
  }
4203
4226
  catch { /* dead */ }
4204
- let ageMs = Infinity;
4205
- try {
4206
- ageMs = Date.now() - fs.statSync(path.join(dir, f)).mtimeMs;
4207
- }
4208
- catch { /* */ }
4209
4227
  // Live PID + recent mtime → genuine sibling owner, leave it.
4210
4228
  // Live PID + ancient mtime → recycled PID, sweep. Dead PID → sweep.
4211
4229
  if (alive && ageMs < STALE_CLAIM_MS)
@@ -4338,7 +4356,15 @@ export class ImapManager extends EventEmitter {
4338
4356
  }
4339
4357
  try {
4340
4358
  const raw = fs.readFileSync(claimedPath, "utf-8");
4341
- await client.appendMessage(outboxPath, raw, ["\\Seen"]);
4359
+ // Bound the APPEND. On a wedged connection (Dovecot
4360
+ // ETIMEDOUT storm) the bare await can hang the full
4361
+ // 300s inactivity timeout, pinning the file in
4362
+ // `.sending-` state the whole time and reading to the
4363
+ // user as "stuck, not sending" (Bob 2026-06-11). A
4364
+ // 60s cap force-closes the socket and throws, so the
4365
+ // catch below releases the claim and the next tick
4366
+ // retries instead of hanging for 5 minutes.
4367
+ await withTimeout(client.appendMessage(outboxPath, raw, ["\\Seen"]), 60_000, client, `outbox APPEND ${file}`);
4342
4368
  fs.renameSync(claimedPath, path.join(sentDir, file));
4343
4369
  console.log(` [outbox] Moved ${file} to IMAP Outbox → sent/`);
4344
4370
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.85",
3
+ "version": "0.1.86",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -12,11 +12,11 @@
12
12
  "@bobfrankston/mailx-types": "^0.1.18",
13
13
  "@bobfrankston/mailx-settings": "^0.1.26",
14
14
  "@bobfrankston/mailx-store": "^0.1.45",
15
- "@bobfrankston/iflow-direct": "^0.1.52",
16
- "@bobfrankston/tcp-transport": "^0.1.6",
17
- "@bobfrankston/smtp-direct": "^0.1.8",
18
- "@bobfrankston/mailx-sync": "^0.1.19",
19
- "@bobfrankston/oauthsupport": "^1.0.31"
15
+ "@bobfrankston/iflow-direct": "^0.1.53",
16
+ "@bobfrankston/tcp-transport": "^0.1.7",
17
+ "@bobfrankston/smtp-direct": "^0.1.9",
18
+ "@bobfrankston/mailx-sync": "^0.1.20",
19
+ "@bobfrankston/oauthsupport": "^1.0.32"
20
20
  },
21
21
  "repository": {
22
22
  "type": "git",
@@ -40,11 +40,11 @@
40
40
  "@bobfrankston/mailx-types": "^0.1.18",
41
41
  "@bobfrankston/mailx-settings": "^0.1.26",
42
42
  "@bobfrankston/mailx-store": "^0.1.45",
43
- "@bobfrankston/iflow-direct": "^0.1.52",
44
- "@bobfrankston/tcp-transport": "^0.1.6",
45
- "@bobfrankston/smtp-direct": "^0.1.8",
46
- "@bobfrankston/mailx-sync": "^0.1.19",
47
- "@bobfrankston/oauthsupport": "^1.0.31"
43
+ "@bobfrankston/iflow-direct": "^0.1.53",
44
+ "@bobfrankston/tcp-transport": "^0.1.7",
45
+ "@bobfrankston/smtp-direct": "^0.1.9",
46
+ "@bobfrankston/mailx-sync": "^0.1.20",
47
+ "@bobfrankston/oauthsupport": "^1.0.32"
48
48
  }
49
49
  }
50
50
  }