@bobfrankston/rmfmail 1.1.128 → 1.1.130

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/.commitmsg ADDED
@@ -0,0 +1,16 @@
1
+ feat: -send <file> CLI + general-outbox auto-routing; draft UI fixes
2
+
3
+ - rmfmail -send <file> [-account <id>]: enqueue a .ltr/.eml from disk for
4
+ sending. With -account, drops into outbox/<id>/; without, drops into the
5
+ general ~/.rmfmail/outbox/ and the daemon's outbox sweep auto-routes by
6
+ parsing the file's From: header (exact match → known-provider domain →
7
+ first non-known-provider "override" account). Bcc preserved as-is;
8
+ existing SMTP/IMAP-Outbox paths handle the strip.
9
+ - Draft UI: clicking a Drafts row no longer auto-pops compose — the
10
+ existing "Edit Draft" button (and double-click) are the only entry
11
+ points. On Send, the draft row drops from the list immediately (was a
12
+ 2 s debounce). New "Sent HH:MM" / "Send failed: …" status confirmation.
13
+ - CLI: -send / -account added to knownFlags. Top-of-file Usage comment and
14
+ unknown-flag error string rewritten to be complete and consistent —
15
+ single-dash is canonical, double-dash still accepted as a synonym.
16
+ README "Command Line" section refreshed in lockstep.
package/README.md CHANGED
@@ -246,22 +246,79 @@ All AI features are opt-in. Provider + API key live in the autocomplete settings
246
246
 
247
247
  ## Command Line
248
248
 
249
+ Flag style: single-dash is canonical (`-kill`, `-setup`, ...). The parser
250
+ also accepts double-dash (`--kill`, `--setup`) as a synonym, so older
251
+ scripts that used `--server` / `--email` keep working.
252
+
249
253
  ```
250
254
  rmfmail Start the app (native window via IPC)
251
- rmfmail --email <addr> First-time setup with this email (skips prompt)
252
- rmfmail --verbose Show log output in terminal (default: log file only)
253
- rmfmail --import <file> Import accounts.jsonc into Google Drive
254
- rmfmail --server Start HTTP server for dev/remote (http://localhost:9333)
255
-
256
- rmfmail -kill Kill running rmfmail processes + clean up WAL files
257
- rmfmail -repair Re-sync message metadata (fix garbled subjects)
258
- rmfmail -rebuild Wipe local cache and re-download everything from IMAP
259
- rmfmail -setup Interactive first-time setup (CLI)
260
- rmfmail -test Test IMAP/SMTP connectivity for all accounts
261
- rmfmail -reauth Clear cached OAuth tokens; next start re-consents
262
- rmfmail -v Show version
255
+ rmfmail -email <addr> First-time setup with this email (skips prompt)
256
+ rmfmail -verbose Show log output in terminal (default: log file only)
257
+ rmfmail -import <file> Import accounts.jsonc into Google Drive
258
+ rmfmail -server Start HTTP server for dev/remote (http://localhost:9333)
259
+
260
+ rmfmail -kill Kill running rmfmail processes + clean up WAL files
261
+ rmfmail -repair Re-sync message metadata (fix garbled subjects)
262
+ rmfmail -rebuild Wipe local cache and re-download everything from IMAP
263
+ rmfmail -setup Interactive first-time setup (CLI)
264
+ rmfmail -test Test IMAP/SMTP connectivity for all accounts
265
+ rmfmail -reauth Clear cached OAuth tokens; next start re-consents
266
+ rmfmail -v Show version
267
+
268
+ rmfmail -send <file> Enqueue a .ltr/.eml for sending. Daemon parses
269
+ the file's From: header to auto-route to a
270
+ configured account; falls back to the first
271
+ "override" account (non-known-provider domain).
272
+ rmfmail -send <file> -account <id>
273
+ Same, but force the account.
274
+
275
+ rmfmail -register-mailto Register rmfmail as a Windows mailto: handler
276
+ rmfmail -unregister-mailto Remove the mailto: handler registration
263
277
  ```
264
278
 
279
+ ### -send
280
+
281
+ Drop a raw `.ltr` or `.eml` file into the outbox without going through
282
+ compose. Two paths under the hood:
283
+
284
+ - `rmfmail -send <file>` copies into `~/.rmfmail/outbox/<basename>` (general
285
+ outbox). The running daemon's outbox sweep parses `From:` and routes the
286
+ file into `outbox/<accountId>/` based on:
287
+ 1. exact match against an account's email,
288
+ 2. known-provider domain match (gmail.com → first Gmail account; outlook.com /
289
+ hotmail.com / live.com → first Outlook account),
290
+ 3. first account on a non-known-provider domain (the "override" account).
291
+
292
+ - `rmfmail -send <file> -account <id>` skips routing and copies directly
293
+ into `~/.rmfmail/outbox/<id>/`.
294
+
295
+ Bcc handling: the file's `Bcc:` header is preserved on disk. The SMTP path
296
+ (Gmail accounts) strips it from the wire bytes and adds the Bcc addresses
297
+ to the SMTP envelope; the IMAP-Outbox path (Dovecot/etc.) uploads the file
298
+ intact and lets the server-side outbox-drain sieve do the strip.
299
+
300
+ The CLI exits immediately. If the daemon isn't running, the file queues
301
+ until next launch. To send right now, also run `rmfmail` (or have a daemon
302
+ already running) — the outbox sweep ticks every 10 s.
303
+
304
+ ### Mailto handler (Windows)
305
+
306
+ `rmfmail -register-mailto` writes the HKCU registry keys so rmfmail
307
+ appears in the Windows "Select an app to open this 'mailto' link" picker
308
+ (and in Settings → Apps → Default apps). The picker entry launches
309
+ `%LOCALAPPDATA%\rmfmail\bin\rmfmailto.exe` which shells out to the rmfmail
310
+ daemon with the parsed mailto URL.
311
+
312
+ To make rmfmail the *active default* for mailto (rather than just a
313
+ candidate), open Settings → Apps → Default apps, search for rmfmail, and
314
+ click "Set default" for the mailto link type. Windows 11 enforces a hash
315
+ on the UserChoice key so this final step can't be automated without
316
+ MSIX packaging or a hash-aware helper.
317
+
318
+ `rmfmail -unregister-mailto` removes the registration. Run this before
319
+ `-register-mailto` if the picker is showing a stale "Node.js JavaScript
320
+ Runtime" entry from an older install.
321
+
265
322
  ### -repair vs -rebuild
266
323
 
267
324
  **-repair** clears message metadata (subjects, flags, sender names) from the database but preserves your downloaded `.eml` message files. Folder sync state resets so rmfmail re-fetches all envelopes on next run. Use this when subjects show garbled characters.
package/bin/mailx.js CHANGED
@@ -2,21 +2,49 @@
2
2
  /**
3
3
  * rmfmail -- email client
4
4
  *
5
- * Usage:
6
- * rmfmail Start service + open in msger (IPC, no TCP)
7
- * rmfmail --server Start Express HTTP server (dev/remote)
8
- * rmfmail --no-browser Start server only (headless)
9
- * rmfmail --verbose Show console output (default: log file only)
10
- * rmfmail --email <addr> First-time setup with email (skips prompt)
11
- * rmfmail --import <file> Import accounts.jsonc into GDrive and merge
12
- * rmfmail -v / --version Show version and exit
13
- * rmfmail -kill Kill running rmfmail processes
14
- * rmfmail -setup Interactive first-time setup (CLI)
15
- * rmfmail -test Test IMAP/SMTP connectivity
16
- * rmfmail -rebuild Wipe local cache, re-sync from IMAP
17
- * rmfmail -repair Re-sync metadata (fix corrupt subjects) keeping .eml files
18
- * rmfmail -reauth Clear cached OAuth tokens; next start re-consents
19
- * (use when new Google scopes have been added)
5
+ * Flag style: single-dash is canonical (-kill, -setup, ...). The parser also
6
+ * accepts double-dash (--kill, --setup) as a synonym for every flag — old
7
+ * scripts that used --server / --email / etc. keep working.
8
+ *
9
+ * Launch:
10
+ * rmfmail Start daemon + open WebView (IPC, no TCP)
11
+ * rmfmail -server Start Express HTTP server (dev/remote)
12
+ * rmfmail -no-browser Server mode without auto-opening a browser
13
+ * rmfmail -another Don't replace any existing rmfmail daemon
14
+ * rmfmail -verbose Echo logs to terminal (default: log file only)
15
+ * rmfmail -debug-server Launch debug introspection server
16
+ *
17
+ * Commands (exit immediately):
18
+ * rmfmail -kill Kill running rmfmail daemon + WebView
19
+ * rmfmail -v / -version Show version and exit
20
+ * rmfmail -setup Interactive first-time account setup (CLI)
21
+ * rmfmail -add Add another account (CLI)
22
+ * rmfmail -test Test IMAP/SMTP connectivity for all accounts
23
+ * rmfmail -rebuild Wipe local cache, re-sync everything from IMAP
24
+ * rmfmail -repair Re-sync metadata (fix corrupt subjects), keeps .eml files
25
+ * rmfmail -reauth Clear cached OAuth tokens; next start re-consents
26
+ * (use when new Google scopes have been added)
27
+ * rmfmail -log Print log file path and exit
28
+ * rmfmail -email <addr> First-time setup with this email (skips prompt)
29
+ * rmfmail -import <file> Import accounts.jsonc into GDrive and merge
30
+ * rmfmail -send <file> Enqueue a .ltr/.eml for sending — daemon auto-routes
31
+ * by parsing the file's `From:` header against
32
+ * configured accounts. Falls back to the first
33
+ * "override" account (non-known-provider domain).
34
+ * rmfmail -send <file> -account <id>
35
+ * Same, but force the account (skips auto-routing).
36
+ *
37
+ * Mailto handler (Windows):
38
+ * rmfmail -register-mailto Register as the OS-level mailto: handler so
39
+ * rmfmail appears in the "Open this 'mailto'
40
+ * link" picker (Settings → Default apps to set
41
+ * it as the active default).
42
+ * rmfmail -unregister-mailto Remove the mailto: handler registration.
43
+ * rmfmail -mailto <url> (internal) Decode a mailto URL and open compose.
44
+ *
45
+ * Internal:
46
+ * rmfmail -daemon (internal) marker for the detached daemon respawn.
47
+ * rmfmail -allow-elevated Bypass the admin/root refusal (debug only).
20
48
  */
21
49
  import fs from "node:fs";
22
50
  import path from "node:path";
@@ -109,6 +137,45 @@ const isDaemon = hasFlag("daemon"); // internal: re-spawned detached process
109
137
  }
110
138
  }
111
139
  }
140
+ // -send <file> [-account <id>]: enqueue a .ltr/.eml from disk for sending.
141
+ // Just drops the file into `~/.rmfmail/outbox/` (or `~/.rmfmail/outbox/<id>/`
142
+ // when -account is given) and exits. The running daemon's outbox sweep picks
143
+ // it up within 10 s; if the daemon isn't running, the file queues until next
144
+ // launch. Routing (auto-account from `From:` header) is handled by
145
+ // ImapManager.routeGeneralOutbox() inside the daemon — keeps the CLI thin.
146
+ {
147
+ const sendIdx = args.findIndex(a => a === "-send" || a === "--send");
148
+ if (sendIdx !== -1) {
149
+ const file = args[sendIdx + 1];
150
+ if (!file) {
151
+ console.error("Usage: rmfmail -send <file> [-account <accountId>]");
152
+ process.exit(1);
153
+ }
154
+ if (!fs.existsSync(file)) {
155
+ console.error(`rmfmail -send: file not found: ${file}`);
156
+ process.exit(1);
157
+ }
158
+ const acctIdx = args.findIndex(a => a === "-account" || a === "--account");
159
+ const accountId = acctIdx !== -1 ? args[acctIdx + 1] : "";
160
+ const { getConfigDir } = await import("@bobfrankston/mailx-settings");
161
+ const outboxRoot = path.join(getConfigDir(), "outbox");
162
+ const targetDir = accountId ? path.join(outboxRoot, accountId) : outboxRoot;
163
+ fs.mkdirSync(targetDir, { recursive: true });
164
+ const now = new Date();
165
+ const pad2 = (n) => String(n).padStart(2, "0");
166
+ const ts = `${now.getFullYear()}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}_${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}-${String(Math.floor(Math.random() * 10000)).padStart(4, "0")}.ltr`;
167
+ const target = path.join(targetDir, ts);
168
+ fs.copyFileSync(file, target);
169
+ if (accountId) {
170
+ console.log(`Queued ${path.basename(file)} → outbox/${accountId}/${ts}`);
171
+ }
172
+ else {
173
+ console.log(`Queued ${path.basename(file)} → outbox/${ts} (daemon will auto-route by From: header)`);
174
+ }
175
+ console.log("Outbox sweep picks it up within 10 s (start rmfmail if it isn't running).");
176
+ process.exit(0);
177
+ }
178
+ }
112
179
  if (hasFlag("register-mailto") || hasFlag("unregister-mailto")) {
113
180
  const unregister = hasFlag("unregister-mailto");
114
181
  if (process.platform !== "win32") {
@@ -426,13 +493,41 @@ const rebuildMode = hasFlag("rebuild");
426
493
  const repairMode = hasFlag("repair");
427
494
  const importMode = hasFlag("import");
428
495
  // Validate arguments
429
- const knownFlags = ["verbose", "kill", "v", "version", "setup", "add", "test", "rebuild", "repair", "log", "import", "email", "mail", "daemon", "reauth", "mailto", "register-mailto", "unregister-mailto", "allow-elevated", "another", "server", "debug-server"];
496
+ const knownFlags = ["verbose", "kill", "v", "version", "setup", "add", "test", "rebuild", "repair", "log", "import", "email", "mail", "daemon", "reauth", "mailto", "register-mailto", "unregister-mailto", "allow-elevated", "another", "server", "no-browser", "debug-server", "send", "account"];
430
497
  for (const arg of args) {
431
498
  // Strip a leading -/-- and any `=value` suffix before checking.
432
499
  const flag = arg.replace(/^--?/, "").split("=")[0];
433
500
  if (arg.startsWith("-") && !knownFlags.includes(flag)) {
434
501
  console.error(`Unknown option: ${arg}`);
435
- console.error("Usage: rmfmail [-verbose] [-kill] [-rebuild] [-v|-version] [-setup]");
502
+ console.error("Usage: rmfmail [options] (-- prefix also accepted for every flag)\n" +
503
+ " Launch:\n" +
504
+ " (no args) Start daemon + open WebView (IPC, no TCP)\n" +
505
+ " -server Start Express HTTP server (dev/remote)\n" +
506
+ " -no-browser Server mode without auto-opening a browser\n" +
507
+ " -another Don't replace any existing rmfmail daemon\n" +
508
+ " -verbose Echo logs to terminal (default: log file only)\n" +
509
+ " -debug-server Launch debug introspection server\n" +
510
+ " Commands (exit immediately):\n" +
511
+ " -kill Kill running rmfmail daemon + WebView\n" +
512
+ " -v / -version Print version and exit\n" +
513
+ " -setup Interactive first-time account setup (CLI)\n" +
514
+ " -add Add another account (CLI)\n" +
515
+ " -test Test IMAP/SMTP connectivity for all accounts\n" +
516
+ " -rebuild Wipe local cache, re-sync everything\n" +
517
+ " -repair Re-sync metadata (keeps .eml files)\n" +
518
+ " -reauth Clear cached OAuth tokens; re-consent on next start\n" +
519
+ " -log Print log file path and exit\n" +
520
+ " -email <addr> First-time setup with this email (skips prompt)\n" +
521
+ " -import <file> Import accounts.jsonc into GDrive and merge\n" +
522
+ " -send <file> Enqueue a .ltr/.eml for sending (auto-route by From:)\n" +
523
+ " -send <file> -account <id> Enqueue under a specific account\n" +
524
+ " Mailto handler (Windows):\n" +
525
+ " -register-mailto Register as the OS-level mailto: handler\n" +
526
+ " -unregister-mailto Remove the mailto: handler registration\n" +
527
+ " -mailto <url> (internal) Decode a mailto URL and open compose\n" +
528
+ " Internal:\n" +
529
+ " -daemon (internal) marker for the detached daemon respawn\n" +
530
+ " -allow-elevated Bypass the admin/root refusal (debug only)");
436
531
  process.exit(1);
437
532
  }
438
533
  }