@bobfrankston/rmfmail 1.2.79 → 1.2.81
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/README.md +64 -22
- package/bin/mailx.js +24 -18
- package/bin/mailx.js.map +1 -1
- package/bin/mailx.ts +23 -18
- package/bin/popout-server.js +147 -0
- package/bin/popout-server.js.map +1 -0
- package/bin/popout-server.ts +149 -0
- package/client/app.bundle.js +19 -2
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +2 -1
- package/client/app.js.map +1 -1
- package/client/app.ts +2 -1
- package/client/components/message-viewer.js +31 -2
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +27 -2
- package/client/index.html +1 -0
- package/package.json +8 -14
package/README.md
CHANGED
|
@@ -15,9 +15,35 @@ npm install -g @bobfrankston/rmfmail
|
|
|
15
15
|
rmfmail
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Requires Node.js
|
|
18
|
+
Requires Node.js 24.2 or later.
|
|
19
19
|
|
|
20
|
-
On Windows, the native WebView2 app launches automatically via IPC (no HTTP server needed).
|
|
20
|
+
On Windows, the native WebView2 app launches automatically via IPC (no HTTP server needed).
|
|
21
|
+
|
|
22
|
+
## Updating
|
|
23
|
+
|
|
24
|
+
Update with [`@bobfrankston/bobup`](https://www.npmjs.com/package/@bobfrankston/bobup) — it lists and updates globally-installed `@bobfrankston/*` packages (cross-platform):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install -g @bobfrankston/bobup # once
|
|
28
|
+
bobup # update rmfmail + any other @bobfrankston tools to latest
|
|
29
|
+
bobup update rmfmail # update only rmfmail
|
|
30
|
+
bobup list # preview what's outdated first
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
After updating, restart the app to pick up the new version: `rmfmail -kill` then `rmfmail`. (Plain `npm install -g @bobfrankston/rmfmail` still works as well.)
|
|
34
|
+
|
|
35
|
+
## Platforms
|
|
36
|
+
|
|
37
|
+
| Platform | Status | How it runs |
|
|
38
|
+
|----------|--------|-------------|
|
|
39
|
+
| Windows | ✅ | Native WebView2 window via the **msger** host (default); IPC, no HTTP server |
|
|
40
|
+
| Linux | ✅ | **msger** host (WebKit WebView) |
|
|
41
|
+
| macOS | ⏳ planned | **msgview** (Electron) host — set `MAILX_HOST=msgview` once the adapter ships |
|
|
42
|
+
| Android | ✅ | Standalone **MAUI** app — its own local store + direct IMAP/Gmail sync; side-load the APK |
|
|
43
|
+
|
|
44
|
+
The host is chosen at runtime via `@bobfrankston/mailx-host` (override with the `MAILX_HOST` env var). The Android app is a full standalone client — not a remote view of the desktop — so it works with the desktop turned off.
|
|
45
|
+
|
|
46
|
+
> **`--server` (Express HTTP/browser) mode was removed (2026-06).** The client became IPC-only, leaving the browser path with no transport, so the mode was dead. Use the native desktop app or the standalone Android app instead.
|
|
21
47
|
|
|
22
48
|
## First-Time Setup
|
|
23
49
|
|
|
@@ -114,20 +140,17 @@ rmfmail --import my-accounts.jsonc
|
|
|
114
140
|
|
|
115
141
|
This merges the accounts into your Google Drive settings (deduplicates by email address). Existing accounts are preserved.
|
|
116
142
|
|
|
117
|
-
### Gmail OAuth
|
|
143
|
+
### Gmail OAuth
|
|
118
144
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
3. Create **OAuth 2.0 credentials** (Desktop app type)
|
|
123
|
-
4. Download `credentials.json` to the iflow package directory
|
|
124
|
-
5. First connection opens a browser for consent. Tokens refresh automatically.
|
|
145
|
+
No setup required for normal use — rmfmail **ships with OAuth credentials**, so the first Gmail connection just opens a browser for consent and tokens refresh automatically.
|
|
146
|
+
|
|
147
|
+
**Advanced / self-hosting (optional):** to use your *own* OAuth app instead of the bundled credentials, create a project in the [Google Cloud Console](https://console.cloud.google.com/), enable the **Gmail API** (and **People API** for contacts), create **OAuth 2.0 credentials** (Desktop app type), and save them as `~/.rmfmail/google-credentials.json`. Outlook/Graph works the same way via `~/.rmfmail/microsoft-credentials.json`.
|
|
125
148
|
|
|
126
149
|
## Usage
|
|
127
150
|
|
|
128
151
|
### Layout
|
|
129
152
|
|
|
130
|
-
A vertical **icon rail** sits on the far left (Thunderbird / Dovecot style) with one-click access to Compose, Inbox, All Inboxes, and Settings
|
|
153
|
+
A vertical **icon rail** sits on the far left (Thunderbird / Dovecot style) with one-click access to Compose, Inbox, All Inboxes, Contacts (Google address book), Calendar, Tasks, View options, and Settings. Calendar/Tasks open the right-side sidebar (Google Calendar + Tasks); Contacts opens the address book. The rail is always visible on wide and medium screens; it folds into the hamburger menu on narrow ones.
|
|
131
154
|
|
|
132
155
|
### Reading Mail
|
|
133
156
|
|
|
@@ -159,10 +182,12 @@ A vertical **icon rail** sits on the far left (Thunderbird / Dovecot style) with
|
|
|
159
182
|
|
|
160
183
|
- **Delete** or **Ctrl+D** -- Delete selected messages (moves to Trash; second delete in Trash is a hard delete + EXPUNGE)
|
|
161
184
|
- **Ctrl+Z** -- Undo the last **delete or move** (whichever came last, 60s window)
|
|
162
|
-
- **Ctrl+A** -- Select all messages
|
|
185
|
+
- **Ctrl+A** -- Select all messages (enters multi-select; row avatars turn into checkboxes). **Escape** or a plain click exits multi-select.
|
|
186
|
+
- **Multi-select actions** -- With rows selected, right-click → **Flag/Unflag**, **Mark read/unread**, or **Move** acts on the whole selection. Clicking the **star** on any selected row flags/unflags the *entire* selection at once.
|
|
187
|
+
- **R** or **Ctrl+Q** -- Toggle read/unread on the focused message (bare R defers while typing in a field; Ctrl+Q always works). Auto mark-as-read (with a configurable delay) is on by default — see Settings.
|
|
163
188
|
- **Drag and drop** -- Move messages to a folder by dragging them
|
|
164
189
|
- **Right-click a message → Move to folder…** -- Searchable folder picker; useful on narrow layouts where the folder tree is hidden
|
|
165
|
-
- Click the **star** column to flag/unflag a message
|
|
190
|
+
- Click the **star** column to flag/unflag a single message
|
|
166
191
|
- **Unsubscribe** button appears when the message has a List-Unsubscribe header. One-click (RFC 8058) when the sender advertises `List-Unsubscribe-Post: List-Unsubscribe=One-Click`; otherwise opens the URL or a pre-filled compose for `mailto:` lists.
|
|
167
192
|
- **Right-click on a From/To/Cc address** -- Copy name, Copy address, Copy both, Add to contacts, or Reply/Reply All/Forward
|
|
168
193
|
- **Right-click in the message body → Translate** (opt-in) -- Uses the configured AI provider; select text first to translate just the selection. Off by default; enable under **Settings → AI translate**.
|
|
@@ -179,6 +204,8 @@ Type in the search bar and press **Enter**. Use the dropdown to search:
|
|
|
179
204
|
|
|
180
205
|
Supports regex: wrap pattern in `/slashes/`.
|
|
181
206
|
|
|
207
|
+
**Search as find:** selecting a result then closing the search (clear the box or press **Escape**) returns you to that message in its real folder, instead of bouncing back to where you started — so search doubles as a "jump to this message" tool. Search is **ephemeral**: it never persists across a restart. An empty result reads *No messages found by "query"* so it's clear the search (not the mailbox) is empty.
|
|
208
|
+
|
|
182
209
|
### Folder Operations
|
|
183
210
|
|
|
184
211
|
Right-click a folder for options:
|
|
@@ -187,19 +214,29 @@ Right-click a folder for options:
|
|
|
187
214
|
- Rename / Delete folder
|
|
188
215
|
- Empty (Trash and Junk only)
|
|
189
216
|
|
|
217
|
+
Drag a folder onto another to **reparent** it (server-side IMAP `RENAME` / Gmail label / Outlook move).
|
|
218
|
+
|
|
190
219
|
### View Options
|
|
191
220
|
|
|
192
221
|
Under **View** in the toolbar:
|
|
193
222
|
- **Two-line view** -- Show preview snippet below each subject
|
|
194
223
|
- **Preview pane** -- Toggle the message reader panel
|
|
195
224
|
- **Preview snippets** -- Show snippet text in message rows
|
|
196
|
-
- **
|
|
225
|
+
- **Flag stars** -- Show the grey ☆ on every row (on by default). Turn it off to hide empty stars; flagged ★ rows keep their gold star, and a row's ☆ reappears on hover so you can still click to flag.
|
|
226
|
+
- **Group by thread** -- Collapse reply chains to the newest message with a count pill; click the pill for a popup of the whole conversation
|
|
227
|
+
- **Only this conversation** -- Filter the list to the selected message's thread
|
|
228
|
+
- **Flagged only** -- Show only starred messages (the list takes a flag-coloured wash so the filter is obvious). Session-only — resets on restart.
|
|
229
|
+
- **Priority senders only** -- Show only mail from senders/domains marked priority in `contacts.jsonc`
|
|
197
230
|
- **Folder counts** -- Show total message counts on all folders
|
|
231
|
+
- **Calendar sidebar** -- Show the right-side calendar / tasks panel
|
|
198
232
|
|
|
199
233
|
### Settings
|
|
200
234
|
|
|
201
235
|
Under **Settings** in the toolbar:
|
|
202
|
-
- **Editor** -- Choose between Quill (default)
|
|
236
|
+
- **Editor** -- Choose between Quill (default), tiptap, or TinyMCE for compose
|
|
237
|
+
- **External editor (Edit in Word)** -- Open the compose body in Microsoft Word or LibreOffice Writer for heavy editing (Auto / Word / LibreOffice)
|
|
238
|
+
- **Auto mark-as-read** -- Mark a viewed message read after a short, configurable delay (delay in seconds; 0 = immediately). Turn off to require an explicit Mark-read.
|
|
239
|
+
- **Check sender reputation (DNSBLs)** -- When opening a message with remote content, look the sender's domain up in three free blocklists in parallel (off by default; each query leaks the bare domain to that DNSBL)
|
|
203
240
|
- **AI autocomplete** -- Enable LLM-powered writing suggestions (Ollama, Claude, or OpenAI)
|
|
204
241
|
- **AI translate** -- Enable the right-click Translate item in the message viewer (off by default; uses the same provider as autocomplete)
|
|
205
242
|
- **AI proofread** -- Enable the proofread path (off by default; provider method available, UI wiring in progress)
|
|
@@ -216,9 +253,10 @@ All AI features are opt-in. Provider + API key live in the autocomplete settings
|
|
|
216
253
|
| Ctrl+F | Forward |
|
|
217
254
|
| Delete / Ctrl+D | Delete |
|
|
218
255
|
| Ctrl+Z | Undo last delete or move |
|
|
219
|
-
| Ctrl+A | Select all |
|
|
256
|
+
| Ctrl+A | Select all (enter multi-select) |
|
|
257
|
+
| R / Ctrl+Q | Toggle read/unread on the focused message |
|
|
220
258
|
| F5 | Sync all folders |
|
|
221
|
-
| Escape |
|
|
259
|
+
| Escape | Exit multi-select / clear search / close menus |
|
|
222
260
|
|
|
223
261
|
**In the compose editor:**
|
|
224
262
|
|
|
@@ -248,21 +286,24 @@ All AI features are opt-in. Provider + API key live in the autocomplete settings
|
|
|
248
286
|
|
|
249
287
|
Flag style: single-dash is canonical (`-kill`, `-setup`, ...). The parser
|
|
250
288
|
also accepts double-dash (`--kill`, `--setup`) as a synonym, so older
|
|
251
|
-
scripts that used `--
|
|
289
|
+
scripts that used `--email` / `--import` keep working.
|
|
252
290
|
|
|
253
291
|
```
|
|
254
292
|
rmfmail Start the app (native window via IPC)
|
|
255
293
|
rmfmail -email <addr> First-time setup with this email (skips prompt)
|
|
256
294
|
rmfmail -verbose Show log output in terminal (default: log file only)
|
|
257
295
|
rmfmail -import <file> Import accounts.jsonc into Google Drive
|
|
258
|
-
rmfmail -server Start HTTP server for dev/remote (http://localhost:9333)
|
|
259
296
|
|
|
260
297
|
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
298
|
rmfmail -setup Interactive first-time setup (CLI)
|
|
299
|
+
rmfmail -add Add another account (CLI)
|
|
264
300
|
rmfmail -test Test IMAP/SMTP connectivity for all accounts
|
|
301
|
+
rmfmail -repair Re-sync message metadata, keep .eml files (fix garbled subjects)
|
|
302
|
+
rmfmail -rebuild Wipe local cache and re-download everything from IMAP
|
|
303
|
+
rmfmail -recover Rebuild the DB index from the .eml files on disk (no network)
|
|
304
|
+
rmfmail -fix-flags Reconcile local ★ flags against the server's actual flagged set
|
|
265
305
|
rmfmail -reauth Clear cached OAuth tokens; next start re-consents
|
|
306
|
+
rmfmail -log Print the log file path and exit
|
|
266
307
|
rmfmail -v Show version
|
|
267
308
|
|
|
268
309
|
rmfmail -send <file> Enqueue a .ltr/.eml for sending. Daemon parses
|
|
@@ -319,12 +360,14 @@ MSIX packaging or a hash-aware helper.
|
|
|
319
360
|
`-register-mailto` if the picker is showing a stale "Node.js JavaScript
|
|
320
361
|
Runtime" entry from an older install.
|
|
321
362
|
|
|
322
|
-
### -repair vs -rebuild
|
|
363
|
+
### -repair vs -rebuild vs -recover
|
|
323
364
|
|
|
324
365
|
**-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.
|
|
325
366
|
|
|
326
367
|
**-rebuild** deletes the entire database and message store, then re-downloads everything from IMAP. Accounts and settings are preserved. Use this for a clean start or to reclaim disk space.
|
|
327
368
|
|
|
369
|
+
**-recover** rebuilds the database index purely from the `.eml` files already on disk — no network. Use it to get back everything you had after a DB loss when you'd rather not re-download from the server.
|
|
370
|
+
|
|
328
371
|
## Configuration
|
|
329
372
|
|
|
330
373
|
All config files live in `~/.rmfmail/` (on Windows: `C:\Users\You\.rmfmail\`). On first start, an existing `~/.mailx/` directory is auto-renamed to `~/.rmfmail/`.
|
|
@@ -477,7 +520,6 @@ Use `rmfmail --verbose` to see logs in the terminal instead.
|
|
|
477
520
|
|
|
478
521
|
- **IPC-first** -- Default mode uses msger (Rust/WebView2) with bidirectional IPC. No TCP server, no CLOSE_WAIT zombies.
|
|
479
522
|
- **Host abstraction** -- rmfmail talks to the WebView host through `@bobfrankston/mailx-host`, which picks msger (default) or msgview (Electron, planned for Mac and niche Linux) at runtime. Override with `MAILX_HOST=msger|msgview`. Internal package names retain the historical `mailx-*` prefix; only the public package was renamed.
|
|
480
|
-
- **HTTP fallback** -- `--server` flag starts Express for browser/remote access.
|
|
481
523
|
- **Local-first** -- Changes update local DB immediately; background worker syncs to IMAP.
|
|
482
524
|
- **Offline reading** -- Full message bodies cached as .eml files.
|
|
483
525
|
- **IMAP IDLE** -- Instant new-mail notifications + periodic sync fallback.
|
package/bin/mailx.js
CHANGED
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Launch:
|
|
10
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
11
|
* rmfmail -another Don't replace any existing rmfmail daemon
|
|
14
12
|
* rmfmail -verbose Echo logs to terminal (default: log file only)
|
|
15
13
|
* rmfmail -debug-server Launch debug introspection server
|
|
@@ -498,7 +496,7 @@ const repairMode = hasFlag("repair");
|
|
|
498
496
|
const recoverMode = hasFlag("recover");
|
|
499
497
|
const importMode = hasFlag("import");
|
|
500
498
|
// Validate arguments
|
|
501
|
-
const knownFlags = ["verbose", "kill", "v", "version", "setup", "add", "test", "rebuild", "repair", "recover", "fix-flags", "relink-bodies", "log", "import", "email", "mail", "daemon", "reauth", "mailto", "register-mailto", "unregister-mailto", "allow-elevated", "another", "
|
|
499
|
+
const knownFlags = ["verbose", "kill", "v", "version", "setup", "add", "test", "rebuild", "repair", "recover", "fix-flags", "relink-bodies", "log", "import", "email", "mail", "daemon", "reauth", "mailto", "register-mailto", "unregister-mailto", "allow-elevated", "another", "no-browser", "debug-server", "send", "account"];
|
|
502
500
|
for (const arg of args) {
|
|
503
501
|
// Strip a leading -/-- and any `=value` suffix before checking.
|
|
504
502
|
const flag = arg.replace(/^--?/, "").split("=")[0];
|
|
@@ -507,7 +505,6 @@ for (const arg of args) {
|
|
|
507
505
|
console.error("Usage: rmfmail [options] (-- prefix also accepted for every flag)\n" +
|
|
508
506
|
" Launch:\n" +
|
|
509
507
|
" (no args) Start daemon + open WebView (IPC, no TCP)\n" +
|
|
510
|
-
" -server Start Express HTTP server (dev/remote)\n" +
|
|
511
508
|
" -no-browser Server mode without auto-opening a browser\n" +
|
|
512
509
|
" -another Don't replace any existing rmfmail daemon\n" +
|
|
513
510
|
" -verbose Echo logs to terminal (default: log file only)\n" +
|
|
@@ -1280,7 +1277,6 @@ if (hasFlag("v") || hasFlag("version")) {
|
|
|
1280
1277
|
console.log(` miscinfo ${ver("@bobfrankston/miscinfo")}`);
|
|
1281
1278
|
console.log(` oauth ${ver("@bobfrankston/oauthsupport")}`);
|
|
1282
1279
|
console.log(` store ${ver("@bobfrankston/mailx-store")}`);
|
|
1283
|
-
console.log(` server ${ver("@bobfrankston/mailx-server")}`);
|
|
1284
1280
|
console.log(` api ${ver("@bobfrankston/mailx-api")}`);
|
|
1285
1281
|
console.log(` platform ${process.platform} ${process.arch}`);
|
|
1286
1282
|
process.exit(0);
|
|
@@ -1923,18 +1919,13 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
1923
1919
|
catch (e) {
|
|
1924
1920
|
console.error(` [readme] Could not write sending README: ${e?.message || e}`);
|
|
1925
1921
|
}
|
|
1926
|
-
// `--server`
|
|
1927
|
-
//
|
|
1928
|
-
//
|
|
1929
|
-
//
|
|
1930
|
-
//
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
await import("@bobfrankston/mailx-server");
|
|
1934
|
-
// mailx-server's index.ts self-invokes `start()` — once imported,
|
|
1935
|
-
// Express owns the event loop. Don't fall through to showService.
|
|
1936
|
-
return;
|
|
1937
|
-
}
|
|
1922
|
+
// `--server` (Express HTTP browser mode) was REMOVED 2026-06-29. The client
|
|
1923
|
+
// went IPC-only (everything through the msger `mailxapi` bridge; the old
|
|
1924
|
+
// `fetch("/api/...")` path was deleted), and nothing injected a browser-side
|
|
1925
|
+
// bridge — so the served page could no longer talk to the server and the
|
|
1926
|
+
// mode was effectively dead. The Android app now covers the "use it from a
|
|
1927
|
+
// phone" case it originally existed for. The `mailx-api` Express router lives
|
|
1928
|
+
// on, used only by the optional `--debug-server` dev introspection endpoint.
|
|
1938
1929
|
// Sync runs on its OWN thread now: spawn the sync worker (it hosts
|
|
1939
1930
|
// ImapManager + its own write connection) and hand the main thread a proxy
|
|
1940
1931
|
// that quacks like ImapManager (method calls forwarded over the bus,
|
|
@@ -1958,6 +1949,16 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
1958
1949
|
void syncWorkerHandle;
|
|
1959
1950
|
// Native client is the only option (iflow-direct)
|
|
1960
1951
|
const svc = new MailxService(store, imapManager);
|
|
1952
|
+
// Loopback popout-window server (lets the client window.open a message into
|
|
1953
|
+
// its own OS window). Ephemeral port + per-launch token; best-effort.
|
|
1954
|
+
let popoutInfo = null;
|
|
1955
|
+
try {
|
|
1956
|
+
const { startPopoutServer } = await import("./popout-server.js");
|
|
1957
|
+
popoutInfo = await startPopoutServer(svc);
|
|
1958
|
+
}
|
|
1959
|
+
catch (e) {
|
|
1960
|
+
console.error(` [popout] disabled: ${e?.message || e}`);
|
|
1961
|
+
}
|
|
1961
1962
|
// Inject the popup function so MailxService.showReminderPopup can spawn
|
|
1962
1963
|
// an OS-level always-on-top window via mailx-host. Kept as injection
|
|
1963
1964
|
// (not import) so mailx-service stays host-agnostic.
|
|
@@ -2015,7 +2016,12 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
2015
2016
|
// Open msger in service mode — custom protocol serves files from client dir
|
|
2016
2017
|
const clientDir = path.join(import.meta.dirname, "..", "client");
|
|
2017
2018
|
const mailxapiPath = path.join(clientDir, "lib", "mailxapi.js");
|
|
2018
|
-
|
|
2019
|
+
let mailxapiScript = fs.readFileSync(mailxapiPath, "utf-8");
|
|
2020
|
+
// Tell the client where the popout-window server lives (base URL + token) so
|
|
2021
|
+
// the viewer's "open in separate window" can window.open it.
|
|
2022
|
+
if (popoutInfo) {
|
|
2023
|
+
mailxapiScript += `\nwindow.__rmfPopout=${JSON.stringify({ base: `http://127.0.0.1:${popoutInfo.port}`, token: popoutInfo.token })};`;
|
|
2024
|
+
}
|
|
2019
2025
|
// Restore saved window geometry (position + size) from previous session
|
|
2020
2026
|
const windowJsonPath = path.join(getConfigDir(), "window.json");
|
|
2021
2027
|
let savedGeometry = null;
|