# rmfmail -- Email Client

A local-first email client with IMAP sync, full offline reading, and a standalone native app. Replaces Thunderbird/Outlook.

> **Disclaimer:** This is a personal project written for my own use. I provide it as-is with no promises of support, stability, or fitness for any particular purpose. Use at your own risk.

> **Renamed from `@bobfrankston/mailx`** (2026-05). The old name collided with Linux's BSD `mailx` (`/usr/bin/mailx`) every launch in WSL or on a Linux box. If you have the old package, run `npm install -g @bobfrankston/rmfmail && npm uninstall -g @bobfrankston/mailx`. The old `~/.mailx/` config directory is auto-migrated to `~/.rmfmail/` on first start.

MIT License -- Copyright (c) 2026 Bob Frankston

## Installation

```bash
npm install -g @bobfrankston/rmfmail
rmfmail
```

Requires Node.js 24.2 or later.

On Windows, the native WebView2 app launches automatically via IPC (no HTTP server needed).

## Updating

Update with [`@bobfrankston/bobup`](https://www.npmjs.com/package/@bobfrankston/bobup) — it lists and updates globally-installed `@bobfrankston/*` packages (cross-platform):

```bash
npm install -g @bobfrankston/bobup    # once
bobup                                 # update rmfmail + any other @bobfrankston tools to latest
bobup update rmfmail                  # update only rmfmail
bobup list                            # preview what's outdated first
```

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.)

## Platforms

| Platform | Status | How it runs |
|----------|--------|-------------|
| Windows | ✅ | Native WebView2 window via the **msger** host (default); IPC, no HTTP server |
| Linux | ✅ | **msger** host (WebKit WebView) |
| macOS | ⏳ planned | **msgview** (Electron) host — set `MAILX_HOST=msgview` once the adapter ships |
| Android | ✅ | Standalone **MAUI** app — its own local store + direct IMAP/Gmail sync; side-load the APK |

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.

> **`--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.

## First-Time Setup

On first run, rmfmail shows a setup form. Enter your name and email address.

### Gmail / Google Workspace

No password needed. rmfmail detects Gmail via MX records, opens a browser for OAuth2 sign-in, and auto-configures Google Drive for settings sync. Custom domains hosted on Google Workspace are detected automatically.

### Outlook / Hotmail / Live

No password needed. OAuth2 sign-in opens in your browser.

### Yahoo

Use an **app password** (not your regular password):
1. Go to **Yahoo Account Settings** > **Account Security**
2. Click **Generate app password**
3. Paste the generated password into rmfmail

### AOL

Use an **app password**:
1. Go to **AOL Account Settings** > **Account Security**
2. Click **Generate app password**
3. Paste the generated password into rmfmail

### iCloud

Use an **app-specific password**:
1. Go to [appleid.apple.com](https://appleid.apple.com) > **Sign-In and Security**
2. Click **App-Specific Passwords** > **Generate**
3. Paste the generated password into rmfmail

### Other Providers (Dovecot, Fastmail, etc.)

Enter your IMAP password. rmfmail auto-detects server settings via MX records and DNS. If detection fails, use **Manual setup (advanced)** in the setup form, or create an accounts file and import it (see below).

### Adding Accounts via File

You can create an `accounts.jsonc` file and import it into rmfmail. This is useful for adding accounts that aren't easily configured through the setup form (custom IMAP servers, non-standard ports, etc.).

**Step 1.** Create a file (e.g., `my-accounts.jsonc`) with your account(s):

```jsonc
{
  "accounts": [
    {
      "email": "you@example.com",
      "name": "Your Name",
      "label": "Work",                   // Display name in folder tree
      "imap": {
        "host": "imap.example.com",      // IMAP server hostname
        "port": 993,                     // 993 for TLS (default)
        "tls": true,
        "user": "you@example.com",       // IMAP username (default: email)
        "password": "your-password"
      },
      "smtp": {
        "host": "smtp.example.com",      // SMTP server hostname
        "port": 587,                     // 587 for STARTTLS (default)
        "tls": true,
        "user": "you@example.com",
        "password": "your-password"
      }
    }
  ]
}
```

For known providers, most fields are optional:

```jsonc
{
  "accounts": [
    { "email": "you@gmail.com", "name": "Your Name" },
    { "email": "you@yahoo.com", "name": "Your Name", "password": "app-password-here" },
    {
      "email": "you@yourserver.com",
      "name": "Your Name",
      "label": "Personal",
      "imap": { "host": "imap.yourserver.com", "user": "you", "password": "pass" },
      "smtp": { "host": "smtp.yourserver.com", "user": "you", "password": "pass" }
    }
  ]
}
```

**Step 2.** Import into rmfmail:

```bash
rmfmail --import my-accounts.jsonc
```

This merges the accounts into your Google Drive settings (deduplicates by email address). Existing accounts are preserved.

### Gmail OAuth

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.

**What to expect during Google sign-in:**

- The consent screen lists what rmfmail is asking for (Gmail, Google Drive, Contacts, Calendar) with **checkboxes — every listed item must be checked**. Google sometimes shows them unchecked by default; an unchecked item makes rmfmail repeat the sign-in later for the missing scope.
- **You may be sent to the browser more than once.** Mail, settings storage (Drive), and contacts authorize separately, so two or three consent rounds on first setup is normal — each success page says so and closes itself after a few seconds.
- If sign-in seems stuck, check for a browser window behind other windows waiting for you.

**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`.

## Usage

### Layout

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.

### Reading Mail

- Click a **folder** to see its messages
- Click a **message** to read it in the preview pane
- **All Inboxes** combines inboxes from all accounts (appears with 2+ accounts)
- A small filled teal dot before the date means the message body is downloaded locally (offline-ready); a hollow circle means not yet prefetched. Prefetch runs as a background task every ~60 s independent of folder sync.
- Unread counts show on folders; sub-folder counts bubble up to collapsed parents
- Use the **folder search** box to find folders by name

### Composing

- **Ctrl+N** -- New message
- **Ctrl+R** -- Reply
- **Ctrl+Shift+R** -- Reply All
- **Ctrl+F** -- Forward
- From dropdown lets you pick which account to send from; reply auto-detects which identity to reply from based on which of your addresses the mail was sent to
- Contact autocomplete searches Google Contacts as you type in To/Cc/Bcc
- **Cc / Bcc** are hidden by default — click the toggle buttons next to To to show them
- **Attach** opens a file picker; attachments show as chips with remove buttons
- Drafts auto-save 1.5s after you stop typing, plus a 5s safety-net interval, plus on window close
- Compose window close asks Save / Discard / Cancel if there's content
- Address validation (`local@domain.tld`) runs on To/Cc/Bcc/From before sending — invalid addresses are refused
- **Editor shortcuts**: Ctrl+K insert link, Ctrl+Shift+K remove link, Ctrl+Shift+X strikethrough, Ctrl+Shift+7/8 ordered/bullet list, Ctrl+]/[ indent/outdent, Ctrl+Shift+C color, Ctrl+\ clear formatting. Native spell-check via WebView2 (right-click to add to dictionary).
- **Link editor modal**: Ctrl+K opens a two-field dialog (text + URL) with Remove-link button; hovering any link in the editor shows a floating URL preview
- **Paste URL** auto-links: paste a bare URL over a selection and it wraps it, or paste into empty space to insert as a link

### Managing Messages

- **Delete** or **Ctrl+D** -- Delete selected messages (moves to Trash; second delete in Trash is a hard delete + EXPUNGE)
- **Ctrl+Z** -- Undo the last **delete or move** (whichever came last, 60s window)
- **Ctrl+A** -- Select all messages (enters multi-select; row avatars turn into checkboxes). **Escape** or a plain click exits multi-select.
- **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.
- **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.
- **Drag and drop** -- Move messages to a folder by dragging them
- **Right-click a message → Move to folder…** -- Searchable folder picker; useful on narrow layouts where the folder tree is hidden
- Click the **star** column to flag/unflag a single message
- **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.
- **Right-click on a From/To/Cc address** -- Copy name, Copy address, Copy both, Add to contacts, or Reply/Reply All/Forward
- **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**.
- **Preview pane zoom** -- Ctrl+wheel, Ctrl+= / Ctrl+- / Ctrl+0, or right-click menu (Zoom in/out/reset, Copy, Select all). Persisted across messages.
- **Cross-folder search results** show the folder name for each hit
- **Empty Trash / Empty Junk** -- Right-click the folder in the tree → Empty (confirmation prompt)

### Searching

Type in the search bar and press **Enter**. Use the dropdown to search:
- **All folders** -- Every account and folder
- **This folder** -- Current folder only (filters as you type, server search on Enter)
- **IMAP server** -- Server-side search (useful for messages older than your sync window)

Supports regex: wrap pattern in `/slashes/`.

**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.

### Folder Operations

Right-click a folder for options:
- Mark all read
- New subfolder
- Rename / Delete folder
- Empty (Trash and Junk only)

Drag a folder onto another to **reparent** it (server-side IMAP `RENAME` / Gmail label / Outlook move).

### View Options

Under **View** in the toolbar:
- **Two-line view** -- Show preview snippet below each subject
- **Preview pane** -- Toggle the message reader panel
- **Preview snippets** -- Show snippet text in message rows
- **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.
- **Group by thread** -- Collapse reply chains to the newest message with a count pill; click the pill for a popup of the whole conversation
- **Only this conversation** -- Filter the list to the selected message's thread
- **Flagged only** -- Show only starred messages (the list takes a flag-coloured wash so the filter is obvious). Session-only — resets on restart.
- **Priority senders only** -- Show only mail from senders/domains marked priority in `contacts.jsonc`
- **Folder counts** -- Show total message counts on all folders
- **Calendar sidebar** -- Show the right-side calendar / tasks panel

### Settings

Under **Settings** in the toolbar:
- **Editor** -- Choose between Quill (default), tiptap, or TinyMCE for compose
- **External editor (Edit in Word)** -- Open the compose body in Microsoft Word or LibreOffice Writer for heavy editing (Auto / Word / LibreOffice)
- **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.
- **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)
- **AI autocomplete** -- Enable LLM-powered writing suggestions (Ollama, Claude, or OpenAI)
- **AI translate** -- Enable the right-click Translate item in the message viewer (off by default; uses the same provider as autocomplete)
- **AI proofread** -- Enable the proofread path (off by default; provider method available, UI wiring in progress)

All AI features are opt-in. Provider + API key live in the autocomplete settings; toggling a feature only controls whether that specific capability calls out to the provider.

### Keyboard Shortcuts

| Key | Action |
|-----|--------|
| Ctrl+N | New message |
| Ctrl+R | Reply |
| Ctrl+Shift+R | Reply All |
| Ctrl+F | Forward |
| Delete / Ctrl+D | Delete |
| Ctrl+Z | Undo last delete or move |
| Ctrl+A | Select all (enter multi-select) |
| R / Ctrl+Q | Toggle read/unread on the focused message |
| F5 | Sync all folders |
| Escape | Exit multi-select / clear search / close menus |

**In the compose editor:**

| Key | Action |
|-----|--------|
| Ctrl+K | Insert / edit link (opens dialog with text + URL fields) |
| Ctrl+Shift+K | Remove link |
| Ctrl+B / Ctrl+I / Ctrl+U | Bold / Italic / Underline |
| Ctrl+Shift+X | Strikethrough |
| Ctrl+Shift+7 / 8 | Ordered / Bullet list |
| Ctrl+] / Ctrl+[ | Indent / Outdent |
| Ctrl+Shift+C | Set text color |
| Ctrl+\ | Clear formatting |
| Ctrl+Enter | Send |
| Escape | Close (prompts Save / Discard / Cancel) |

**In the preview pane:**

| Key | Action |
|-----|--------|
| Ctrl+wheel | Zoom in/out |
| Ctrl+= / Ctrl+- | Zoom in / out |
| Ctrl+0 | Reset zoom |
| Delete | Delete message (also works with focus in preview) |

## Command Line

Flag style: single-dash is canonical (`-kill`, `-setup`, ...). The parser
also accepts double-dash (`--kill`, `--setup`) as a synonym, so older
scripts that used `--email` / `--import` keep working.

```
rmfmail                        Start the app (native window via IPC)
rmfmail -email <addr>          First-time setup with this email (skips prompt)
rmfmail -verbose               Show log output in terminal (default: log file only)
rmfmail -import <file>         Import accounts.jsonc into Google Drive

rmfmail -kill                  Kill running rmfmail processes + clean up WAL files
rmfmail -setup                 Interactive first-time setup (CLI)
rmfmail -add                   Add another account (CLI)
rmfmail -test                  Test IMAP/SMTP connectivity for all accounts
rmfmail -repair                Re-sync message metadata, keep .eml files (fix garbled subjects)
rmfmail -rebuild               Wipe local cache and re-download everything from IMAP
rmfmail -recover               Rebuild the DB index from the .eml files on disk (no network)
rmfmail -fix-flags             Reconcile local ★ flags against the server's actual flagged set
rmfmail -reauth                Clear cached OAuth tokens; next start re-consents
rmfmail -log                   Print the log file path and exit
rmfmail -v                     Show version

rmfmail -send <file>           Enqueue a .ltr/.eml for sending. Daemon parses
                               the file's From: header to auto-route to a
                               configured account; falls back to the first
                               "override" account (non-known-provider domain).
rmfmail -send <file> -account <id>
                               Same, but force the account.

rmfmail -register-mailto       Register rmfmail as a Windows mailto: handler
rmfmail -unregister-mailto     Remove the mailto: handler registration
```

### -send

Drop a raw `.ltr` or `.eml` file into the outbox without going through
compose. Two paths under the hood:

- `rmfmail -send <file>` copies into `~/.rmfmail/outbox/<basename>` (general
  outbox). The running daemon's outbox sweep parses `From:` and routes the
  file into `outbox/<accountId>/` based on:
  1. exact match against an account's email,
  2. known-provider domain match (gmail.com → first Gmail account; outlook.com /
     hotmail.com / live.com → first Outlook account),
  3. first account on a non-known-provider domain (the "override" account).

- `rmfmail -send <file> -account <id>` skips routing and copies directly
  into `~/.rmfmail/outbox/<id>/`.

Bcc handling: the file's `Bcc:` header is preserved on disk. The SMTP path
(Gmail accounts) strips it from the wire bytes and adds the Bcc addresses
to the SMTP envelope; the IMAP-Outbox path (Dovecot/etc.) uploads the file
intact and lets the server-side outbox-drain sieve do the strip.

The CLI exits immediately. If the daemon isn't running, the file queues
until next launch. To send right now, also run `rmfmail` (or have a daemon
already running) — the outbox sweep ticks every 10 s.

### Mailto handler (Windows)

`rmfmail -register-mailto` writes the HKCU registry keys so rmfmail
appears in the Windows "Select an app to open this 'mailto' link" picker
(and in Settings → Apps → Default apps). The picker entry launches
`%LOCALAPPDATA%\rmfmail\bin\rmfmailto.exe` which shells out to the rmfmail
daemon with the parsed mailto URL.

To make rmfmail the *active default* for mailto (rather than just a
candidate), open Settings → Apps → Default apps, search for rmfmail, and
click "Set default" for the mailto link type. Windows 11 enforces a hash
on the UserChoice key so this final step can't be automated without
MSIX packaging or a hash-aware helper.

`rmfmail -unregister-mailto` removes the registration. Run this before
`-register-mailto` if the picker is showing a stale "Node.js JavaScript
Runtime" entry from an older install.

### -repair vs -rebuild vs -recover

**-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.

**-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.

**-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.

## Configuration

All config files live in `~/.rmfmail/` (on Windows: `C:\Users\You\.rmfmail\`). On first start, an existing `~/.mailx/` directory is auto-renamed to `~/.rmfmail/`.

### config.jsonc (per-machine, not synced)

Points to shared settings and controls local behavior. Created automatically for Gmail accounts.

```jsonc
{
  "sharedDir": { "provider": "gdrive", "path": "rmfmail", "folderId": "..." },
  "storePath": "C:/Users/You/.rmfmail/mailxstore",
  "historyDays": 90,
  "accountOverrides": {
    "work": { "historyDays": 7 },
    "archive": { "enabled": false }
  }
}
```

**sharedDir** can be:
- `{ "provider": "gdrive", "path": "rmfmail" }` -- Google Drive via API (auto-configured for Gmail). The legacy folder names `mailx` and `home/.mailx` are also discovered automatically for users migrating from the old name.
- `"C:/Users/You/OneDrive/home/.rmfmail"` -- Local/network path

### accounts.jsonc (shared via Google Drive or local)

For known providers, only `email` is required -- IMAP/SMTP settings fill automatically.

```jsonc
{
  "accounts": [
    { "email": "you@gmail.com", "name": "Your Name" },
    { "email": "you@icloud.com", "name": "Your Name", "password": "xxxx-xxxx-xxxx-xxxx" },
    {
      "email": "you@example.com",
      "name": "Your Name",
      "label": "Work",
      "spam": "_spam",                    // Optional: enables the ⚠ Spam button in the viewer toolbar
      "imap": { "host": "imap.example.com", "port": 993, "tls": true, "user": "you", "password": "..." },
      "smtp": { "host": "smtp.example.com", "port": 587, "tls": true, "user": "you", "password": "..." }
    }
  ]
}
```

**Optional per-account fields:**

| Field | Type | Purpose |
|-------|------|---------|
| `spam` | string | IMAP folder path to send messages when the **Spam** (⚠) button is pressed. The button is hidden until this is set. Use the exact folder path on the server (e.g., `"_spam"`, `"INBOX/Spam"`, `"[Gmail]/Spam"`). |
| `label` | string | Display name in the folder tree (overrides auto-detected). |
| `defaultSend` | bool | Use this account's SMTP when From doesn't match any account. |
| `relayDomains` | string[] | Domains to skip in Delivered-To chain (e.g., `["m.connectivity.xyz"]`). |
| `deliveredToPrefix` | string[] | Strip these prefixes from Delivered-To to recover the clean alias (e.g., `["bobf-ma-", "bobf-"]` — order matters, longest first). |
| `identityDomains` | string[] | Domains where Delivered-To becomes the reply From (e.g., `["bob.ma"]`). |

**Auto-detected providers:**

| Domain | IMAP | SMTP | Auth | Label |
|--------|------|------|------|-------|
| gmail.com | imap.gmail.com:993 | smtp.gmail.com:587 | OAuth2 | Gmail |
| outlook.com, hotmail.com | outlook.office365.com:993 | smtp.office365.com:587 | OAuth2 | Outlook |
| yahoo.com | imap.mail.yahoo.com:993 | smtp.mail.yahoo.com:587 | App password | Yahoo |
| aol.com | imap.aol.com:993 | smtp.aol.com:587 | App password | AOL |
| icloud.com | imap.mail.me.com:993 | smtp.mail.me.com:587 | App password | iCloud |

Google Workspace and Microsoft 365 custom domains are detected automatically via MX records.

### preferences.jsonc (shared)

```jsonc
{
  "ui": { "theme": "dark", "editor": "quill", "fontSize": 15 },
  "sync": { "intervalMinutes": 5, "historyDays": 30 },
  "autocomplete": {
    "enabled": false,
    "provider": "ollama",
    "ollamaUrl": "http://localhost:11434",
    "ollamaModel": "qwen2.5-coder:1.5b"
  }
}
```

### allowlist.jsonc (shared)

Remote content (images, tracking pixels) is blocked by default. Whitelist trusted senders:

```jsonc
{
  "senders": ["boss@example.com"],
  "domains": ["example.com"],
  "recipients": ["team@our-group.com"]
}
```

### Multi-Machine Setup

Settings sync via Google Drive API. Each machine has its own `config.jsonc`:

**Desktop** (full history):
```jsonc
{ "sharedDir": { "provider": "gdrive", "path": "rmfmail" }, "historyDays": 0 }
```

**Laptop** (recent only):
```jsonc
{ "sharedDir": { "provider": "gdrive", "path": "rmfmail" }, "historyDays": 30,
  "accountOverrides": { "archive": { "enabled": false } } }
```

Both machines share accounts, preferences, and allowlist via Google Drive.

## Data Storage

| Path | Synced? | Purpose |
|------|---------|---------|
| config.jsonc | No | Local config pointer + overrides |
| accounts.jsonc | Yes | IMAP/SMTP account configs |
| preferences.jsonc | Yes | UI and sync settings |
| allowlist.jsonc | Yes | Remote content allow-list |
| mailx.db | No | SQLite metadata (headers, contacts, sync state) |
| mailxstore/ | No | Cached message bodies (.eml per message) |
| logs/ | No | Daily log files |

Note: the SQLite database file (`mailx.db`) and message-store directory (`mailxstore/`) keep their original filenames inside `~/.rmfmail/` — they're internal to the app and renaming them would break backwards compatibility for no user benefit.

**Safe to delete:** mailxstore/ (re-downloaded on sync), mailx.db (re-created on startup), logs/ (anytime).

## Logs

Daily log files: `~/.rmfmail/logs/mailx-YYYY-MM-DD.log`

Use `rmfmail --verbose` to see logs in the terminal instead.

## Troubleshooting

**Blank window or no response** -- Run `rmfmail -kill` then `rmfmail` to restart.

**OAuth timeout** -- If the browser auth window doesn't appear or times out (5 minutes), close rmfmail and try again.

**Garbled subjects** -- Run `rmfmail -repair` to re-sync metadata from IMAP.

**Sync stuck or corrupt data** -- Run `rmfmail -rebuild` for a clean start. Accounts and settings are preserved.

**"Too many connections"** -- rmfmail uses up to 5 IMAP connections per account. Close other email clients if you hit your server's limit.

**App password rejected (Yahoo/AOL/iCloud)** -- Make sure you're using an app-specific password, not your regular login. Generate a new one from your account security settings.

## Agent API (apps and AI processing your mail)

A running daemon publishes `~/.rmfmail/agent.json` — loopback RPC endpoint, SSE event stream, and two scoped tokens:

- **triage** — reads, search, message bodies, flagging, mark-read. Safe default for AI.
- **full** — the entire service surface (move, delete, drafts, send). Guard it accordingly.

`POST {rpcUrl}?t={token}` with `{"_action": "searchMessages", "_cbid": "1", ...params}` — the same dispatcher the UI uses, so anything the app can do, an agent can do. `GET {eventsUrl}?t={token}` streams daemon events (new mail, folder changes) as SSE for reactive agents.

**MCP:** `rmfmail-mcp` (installed with the package) exposes this as Model Context Protocol tools — `claude mcp add rmfmail -- rmfmail-mcp` and Claude can list/search/read/flag your mail; set `RMFMAIL_MCP_FULL=1` on the server entry to add move + draft creation. By design there is **no send tool**: AI drafts, you send.

## Architecture

- **IPC-first** -- Default mode uses msger (Rust/WebView2) with bidirectional IPC. No TCP server, no CLOSE_WAIT zombies.
- **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.
- **Local-first** -- Changes update local DB immediately; background worker syncs to IMAP.
- **Offline reading** -- Full message bodies cached as .eml files.
- **IMAP IDLE** -- Instant new-mail notifications + periodic sync fallback.
- **Outbox** -- Queued in IMAP Outbox folder with multi-machine interlock via flags.
- **Remote content blocking** -- HTML sanitized server-side, CSP in iframe, per-sender allow-list.
- **Search** -- SQLite FTS5 full-text index + IMAP server search + regex filtering.
- **Connection management** -- Semaphore limits 5 connections per account; exponential backoff on server limits.
- **Cloud sync** -- Google Drive API for settings portability (no filesystem mount required).
