@bobfrankston/mailx-imap 0.1.57 → 0.1.58

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 (3) hide show
  1. package/index.d.ts +15 -0
  2. package/index.js +102 -0
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -473,6 +473,21 @@ export declare class ImapManager extends EventEmitter {
473
473
  /** Start background Outbox worker — runs immediately then every 10 seconds */
474
474
  private outboxBackoff;
475
475
  private outboxBackoffDelay;
476
+ /** Route a file dropped into the general `~/.rmfmail/outbox/` (no acct
477
+ * subdir) to one of the configured accounts. Match order:
478
+ * 1. `From:` address matches an `account.email` exactly (case-insensitive).
479
+ * 2. `From:` domain matches a known-provider domain → first account whose
480
+ * domain matches (gmail.com / googlemail.com → first Gmail account;
481
+ * outlook.com / hotmail.com / live.com → first Outlook account).
482
+ * 3. Default = first account with an explicit IMAP override host (i.e.,
483
+ * not one of the known providers). Bob's bobma is the prototype.
484
+ * Returns the routed accountId or null if no candidate exists. */
485
+ private routeGeneralOutboxFile;
486
+ /** Scan the general outbox (`~/.rmfmail/outbox/*.ltr|*.eml` — no acct
487
+ * subdir) and route each file into the appropriate per-account dir. Runs
488
+ * once per outboxLoop tick before the per-account sweep, so a file
489
+ * manually dropped at the root gets handed off the same tick. */
490
+ private routeGeneralOutbox;
476
491
  startOutboxWorker(): void;
477
492
  /** Stop Outbox worker */
478
493
  stopOutboxWorker(): void;
package/index.js CHANGED
@@ -4213,11 +4213,113 @@ export class ImapManager extends EventEmitter {
4213
4213
  /** Start background Outbox worker — runs immediately then every 10 seconds */
4214
4214
  outboxBackoff = new Map(); // accountId → next retry timestamp
4215
4215
  outboxBackoffDelay = new Map(); // accountId → current delay ms
4216
+ /** Route a file dropped into the general `~/.rmfmail/outbox/` (no acct
4217
+ * subdir) to one of the configured accounts. Match order:
4218
+ * 1. `From:` address matches an `account.email` exactly (case-insensitive).
4219
+ * 2. `From:` domain matches a known-provider domain → first account whose
4220
+ * domain matches (gmail.com / googlemail.com → first Gmail account;
4221
+ * outlook.com / hotmail.com / live.com → first Outlook account).
4222
+ * 3. Default = first account with an explicit IMAP override host (i.e.,
4223
+ * not one of the known providers). Bob's bobma is the prototype.
4224
+ * Returns the routed accountId or null if no candidate exists. */
4225
+ routeGeneralOutboxFile(filePath) {
4226
+ let raw = "";
4227
+ try {
4228
+ raw = fs.readFileSync(filePath, "utf-8");
4229
+ }
4230
+ catch {
4231
+ return null;
4232
+ }
4233
+ const fromMatch = raw.match(/^From:\s*(?:[^<\n]*<\s*)?([^\s<>@]+@[^\s<>]+)/mi);
4234
+ const fromAddr = (fromMatch?.[1] || "").toLowerCase().replace(/[>\s].*$/, "");
4235
+ const fromDomain = fromAddr.split("@")[1] || "";
4236
+ const settings = loadSettings();
4237
+ const accounts = settings.accounts || [];
4238
+ // 1. exact email match
4239
+ if (fromAddr) {
4240
+ const m = accounts.find(a => (a.email || "").toLowerCase() === fromAddr);
4241
+ if (m)
4242
+ return m.id;
4243
+ }
4244
+ // 2. known-provider domain match
4245
+ const GMAIL_DOMAINS = new Set(["gmail.com", "googlemail.com"]);
4246
+ const OUTLOOK_DOMAINS = new Set(["outlook.com", "hotmail.com", "live.com", "msn.com"]);
4247
+ if (fromDomain) {
4248
+ if (GMAIL_DOMAINS.has(fromDomain)) {
4249
+ const m = accounts.find(a => GMAIL_DOMAINS.has((a.email || "").split("@")[1]?.toLowerCase() || ""));
4250
+ if (m)
4251
+ return m.id;
4252
+ }
4253
+ if (OUTLOOK_DOMAINS.has(fromDomain)) {
4254
+ const m = accounts.find(a => OUTLOOK_DOMAINS.has((a.email || "").split("@")[1]?.toLowerCase() || ""));
4255
+ if (m)
4256
+ return m.id;
4257
+ }
4258
+ }
4259
+ // 3. default: first account on a non-known-provider domain (i.e. one
4260
+ // whose IMAP host was explicitly configured rather than auto-detected).
4261
+ const KNOWN_PROVIDER_DOMAINS = new Set([
4262
+ ...GMAIL_DOMAINS, ...OUTLOOK_DOMAINS,
4263
+ "yahoo.com", "aol.com", "icloud.com", "me.com", "mac.com",
4264
+ ]);
4265
+ const override = accounts.find(a => {
4266
+ const d = (a.email || "").split("@")[1]?.toLowerCase() || "";
4267
+ return d && !KNOWN_PROVIDER_DOMAINS.has(d);
4268
+ });
4269
+ if (override)
4270
+ return override.id;
4271
+ // Last resort — any account at all
4272
+ return accounts[0]?.id || null;
4273
+ }
4274
+ /** Scan the general outbox (`~/.rmfmail/outbox/*.ltr|*.eml` — no acct
4275
+ * subdir) and route each file into the appropriate per-account dir. Runs
4276
+ * once per outboxLoop tick before the per-account sweep, so a file
4277
+ * manually dropped at the root gets handed off the same tick. */
4278
+ routeGeneralOutbox() {
4279
+ const root = path.join(getConfigDir(), "outbox");
4280
+ if (!fs.existsSync(root))
4281
+ return;
4282
+ let entries = [];
4283
+ try {
4284
+ entries = fs.readdirSync(root, { withFileTypes: true });
4285
+ }
4286
+ catch {
4287
+ return;
4288
+ }
4289
+ for (const ent of entries) {
4290
+ if (!ent.isFile())
4291
+ continue;
4292
+ if (!/\.(ltr|eml)$/i.test(ent.name))
4293
+ continue;
4294
+ const filePath = path.join(root, ent.name);
4295
+ const accountId = this.routeGeneralOutboxFile(filePath);
4296
+ if (!accountId) {
4297
+ console.error(` [outbox] No account candidate for ${ent.name} — leaving in general outbox`);
4298
+ continue;
4299
+ }
4300
+ const acctDir = path.join(root, accountId);
4301
+ try {
4302
+ fs.mkdirSync(acctDir, { recursive: true });
4303
+ }
4304
+ catch { /* */ }
4305
+ try {
4306
+ fs.renameSync(filePath, path.join(acctDir, ent.name));
4307
+ console.log(` [outbox] Routed ${ent.name} → ${accountId}/`);
4308
+ }
4309
+ catch (e) {
4310
+ console.error(` [outbox] Failed to route ${ent.name}: ${e.message}`);
4311
+ }
4312
+ }
4313
+ }
4216
4314
  startOutboxWorker() {
4217
4315
  if (this.outboxInterval)
4218
4316
  return;
4219
4317
  const processAll = async () => {
4220
4318
  const now = Date.now();
4319
+ // Auto-route any files dropped into the general (acct-agnostic)
4320
+ // outbox — they move into `outbox/<accountId>/` before the
4321
+ // per-account sweep picks them up below.
4322
+ this.routeGeneralOutbox();
4221
4323
  for (const [accountId] of this.configs) {
4222
4324
  // Skip accounts in backoff
4223
4325
  const retryAfter = this.outboxBackoff.get(accountId) || 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.57",
3
+ "version": "0.1.58",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",