@bobfrankston/mailx 1.0.23 → 1.0.24

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 CHANGED
@@ -6,130 +6,144 @@ A local-first email client with IMAP sync, full offline reading, and a standalon
6
6
 
7
7
  MIT License -- Copyright (c) 2026 Bob Frankston
8
8
 
9
- ## Quick Start
9
+ ## Installation
10
10
 
11
11
  ```bash
12
- npm start # Start server (auto-restarts on changes)
13
- # Open http://localhost:9333 in browser
14
-
15
- launch.ps1 # Or use the native WebView2 app
16
- launch.ps1 -restart # Kill existing server and restart
12
+ npm install -g @bobfrankston/mailx
13
+ mailx
17
14
  ```
18
15
 
19
- ## Setup
16
+ Requires Node.js 22 or later (uses built-in `node:sqlite`).
20
17
 
21
- ### 1. Config Pointer (~/.mailx/config.jsonc)
18
+ On Windows, the native WebView2 app launches automatically. On Linux/Mac, it opens in your default browser at `http://127.0.0.1:9333`.
22
19
 
23
- Points to the shared settings file and local store:
20
+ ## First-Time Setup
24
21
 
25
- ```json
26
- {
27
- "settingsPath": "C:/Users/You/OneDrive/home/.mailx/settings.jsonc",
28
- "storePath": "C:/Users/You/.mailx/mailxstore"
29
- }
30
- ```
22
+ ### Option A: Existing user on a new machine (shared settings on OneDrive)
23
+
24
+ If you already have mailx configured on another machine with settings on OneDrive, just install and run. mailx auto-detects shared settings at:
31
25
 
32
- - **settingsPath** — Path to shared settings (can be on OneDrive/Dropbox for multi-machine sync)
33
- - **storePath** — Where cached message bodies are stored (local, not synced)
26
+ - `%OneDrive%/home/.mailx/settings.jsonc`
27
+ - `%OneDriveConsumer%/home/.mailx/settings.jsonc`
28
+ - `~/OneDrive/home/.mailx/settings.jsonc`
34
29
 
35
- If `config.jsonc` doesn't exist, settings default to `~/.mailx/settings.jsonc`.
30
+ Once OneDrive syncs, mailx picks up your accounts automatically.
36
31
 
37
- ### 2. Settings File (settings.jsonc)
32
+ ### Option B: New user -- set up from scratch
33
+
34
+ **Step 1.** Create the settings directory:
35
+
36
+ ```
37
+ mkdir ~/.mailx
38
+ ```
39
+
40
+ (On Windows: `mkdir %USERPROFILE%\.mailx`)
41
+
42
+ **Step 2.** Create `~/.mailx/settings.jsonc` with your account(s):
38
43
 
39
44
  ```jsonc
40
45
  {
41
46
  "accounts": [
42
47
  {
43
- "id": "work", // Unique ID (internal)
44
- "name": "Work Email", // Display name in UI
45
- "email": "bob@example.com",
48
+ "id": "mymail", // Internal ID (no spaces, lowercase)
49
+ "name": "Your Name", // Sender name in From: header
50
+ "label": "Work", // Display label in UI (optional, defaults to name)
51
+ "email": "you@example.com", // Your email address
46
52
  "imap": {
47
- "host": "imap.example.com",
48
- "port": 993,
53
+ "host": "imap.example.com", // IMAP server
54
+ "port": 993, // Usually 993 for SSL/TLS
49
55
  "tls": true,
50
- "auth": "password", // "password" or "oauth2"
51
- "user": "bob",
52
- "password": "secret"
56
+ "auth": "password", // "password" or "oauth2" (Gmail)
57
+ "user": "you@example.com", // IMAP username
58
+ "password": "your-password" // IMAP password (not needed for oauth2)
53
59
  },
54
60
  "smtp": {
55
- "host": "smtp.example.com",
56
- "port": 587,
61
+ "host": "smtp.example.com", // SMTP server
62
+ "port": 587, // Usually 587 for STARTTLS
57
63
  "tls": true,
58
64
  "auth": "password",
59
- "user": "bob",
60
- "password": "secret"
65
+ "user": "you@example.com",
66
+ "password": "your-password"
61
67
  },
62
- "enabled": true
63
- },
64
- {
65
- "id": "gmail",
66
- "name": "Gmail",
67
- "email": "bob@gmail.com",
68
- "imap": {
69
- "host": "imap.gmail.com", "port": 993, "tls": true,
70
- "auth": "oauth2", "user": "bob@gmail.com"
71
- },
72
- "smtp": {
73
- "host": "smtp.gmail.com", "port": 587, "tls": true,
74
- "auth": "oauth2", "user": "bob@gmail.com"
75
- },
76
- "enabled": true
68
+ "enabled": true,
69
+ "defaultSend": true // Use this account for sending when From doesn't match
77
70
  }
78
71
  ],
79
72
  "sync": {
80
- "intervalMinutes": 5, // Full sync interval
81
- "historyDays": 30 // Days of history (0 = all)
82
- },
83
- // UI preferences
84
- "ui": {
85
- "theme": "system", // "system" (follows OS), "dark", or "light"
86
- "folderWidth": 220,
87
- "listViewerSplit": 40,
88
- "fontSize": 15
89
- },
90
- // Auto-populated when you click "Always allow from sender/domain"
91
- "remoteAllowList": {
92
- "senders": [],
93
- "domains": []
73
+ "intervalMinutes": 5, // Full sync interval (minutes)
74
+ "historyDays": 0 // Days of history to sync (0 = all)
94
75
  }
95
76
  }
96
77
  ```
97
78
 
98
- ### 3. Gmail OAuth
79
+ **Step 3.** Run `mailx` and open `http://127.0.0.1:9333` in your browser.
80
+
81
+ ### Option C: Multi-machine with shared settings (OneDrive/Dropbox)
82
+
83
+ Put `settings.jsonc` on a cloud-synced folder, then create a local pointer:
84
+
85
+ **`~/.mailx/config.jsonc`:**
86
+ ```jsonc
87
+ {
88
+ // Point to shared settings on OneDrive (or Dropbox, network share, etc.)
89
+ "sharedDir": "C:/Users/You/OneDrive/home/.mailx",
90
+ // Local-only: where cached messages are stored
91
+ "storePath": "C:/Users/You/.mailx/mailxstore",
92
+ // Per-machine override: days of history (0 = all)
93
+ "historyDays": 90
94
+ }
95
+ ```
96
+
97
+ The shared directory contains: `settings.jsonc`, `allowlist.jsonc`, `preferences.jsonc`. Each machine caches them locally for offline use.
99
98
 
100
- 1. [Google Cloud Console](https://console.cloud.google.com/) → create project
101
- 2. Enable Gmail API (and People API for contacts)
102
- 3. Create OAuth 2.0 credentials (Desktop app)
99
+ ### Adding a Gmail account
100
+
101
+ Gmail requires OAuth2 instead of a password:
102
+
103
+ 1. Go to [Google Cloud Console](https://console.cloud.google.com/) and create a project
104
+ 2. Enable the **Gmail API** (and **People API** for contacts)
105
+ 3. Create **OAuth 2.0 credentials** (Desktop app type)
103
106
  4. Download `credentials.json` to the iflow package directory
104
- 5. First connect opens browser for consent. Tokens cached and refresh automatically.
107
+ 5. Add the Gmail account to `settings.jsonc` with `"auth": "oauth2"`
108
+ 6. First connection opens a browser for OAuth consent. Tokens are cached and refresh automatically.
109
+
110
+ ```jsonc
111
+ {
112
+ "id": "gmail",
113
+ "name": "Your Name",
114
+ "label": "Gmail",
115
+ "email": "you@gmail.com",
116
+ "imap": { "host": "imap.gmail.com", "port": 993, "tls": true, "auth": "oauth2", "user": "you@gmail.com" },
117
+ "smtp": { "host": "smtp.gmail.com", "port": 587, "tls": true, "auth": "oauth2", "user": "you@gmail.com" },
118
+ "enabled": true
119
+ }
120
+ ```
105
121
 
106
122
  ## Usage
107
123
 
108
124
  ### Reading Mail
109
- - **Folder tree** (left) click to view, expand/collapse subfolders
110
- - **Message list** (center) click to preview, scroll for more
111
- - **Preview pane** (right) shows message content
112
- - **All Inboxes** unified view across all accounts
125
+ - **Folder tree** (left) -- click to view, expand/collapse subfolders
126
+ - **Message list** (center) -- click to preview, scroll for more
127
+ - **Preview pane** (right) -- shows message content
128
+ - **All Inboxes** -- unified view across all accounts
129
+ - **Folder search** -- filter box above folder tree to find folders quickly
130
+ - **Message search** -- search bar with scope: This folder / All folders / IMAP server
113
131
 
114
132
  ### Composing
115
- - **Compose** toolbar or Ctrl+N
116
- - **Reply / Reply All / Forward** Ctrl+R / Ctrl+Shift+R / toolbar
117
- - **Send** Ctrl+Enter
118
- - **From field** pick from dropdown or type any address
119
- - **Address autocomplete** type in To/Cc/Bcc, Tab accepts first match, Ctrl+K to trigger
120
- - **Auto-save** drafts saved every 5 seconds
133
+ - **Compose** -- toolbar or Ctrl+N
134
+ - **Reply / Reply All / Forward** -- Ctrl+R / Ctrl+Shift+R / toolbar
135
+ - **Send** -- Ctrl+Enter
136
+ - **From field** -- dropdown with all accounts + "Other..." for custom addresses
137
+ - **Address autocomplete** -- type in To/Cc/Bcc, Tab accepts match
138
+ - **Auto-save** -- drafts saved to IMAP every 5 seconds
121
139
 
122
140
  ### Managing Messages
123
- - **Delete** Del, Ctrl+D, or trash button moves to Trash
124
- - **Undo delete** Ctrl+Z within 30 seconds
125
- - **Flag/unflag** click the star ☆/★
126
- - **Search** type in search bar. Qualifiers: `from:name`, `to:name`, `subject:text`
127
- - **Remote content** blocked by default. Click "Load this time" or "Always from sender/domain"
128
-
129
- ### View Options (View menu)
130
- - **Two-line view** — From+Date on line 1, Subject on line 2
131
- - **Preview pane** — toggle on/off
132
- - **Flagged only** — show only starred messages
141
+ - **Delete** -- Del key or trash button, moves to Trash. Ctrl+Z to undo (30s).
142
+ - **Flag/unflag** -- click the star
143
+ - **Drag and drop** -- drag messages to folders to move them. Shift/Ctrl+click for multi-select.
144
+ - **Remote content** -- blocked by default. "Load once" or "Always" buttons on the banner.
145
+ - **Unsubscribe** -- button appears in header when List-Unsubscribe header is present
146
+ - **View Source** -- Source button copies .eml file path to clipboard
133
147
 
134
148
  ### Keyboard Shortcuts
135
149
  | Key | Action |
@@ -139,31 +153,14 @@ If `config.jsonc` doesn't exist, settings default to `~/.mailx/settings.jsonc`.
139
153
  | Ctrl+Shift+R | Reply All |
140
154
  | Del / Ctrl+D | Delete |
141
155
  | Ctrl+Z | Undo delete |
142
- | Ctrl+K | Address completion |
143
156
  | Ctrl+Enter | Send (compose) |
144
- | Escape | Discard / Clear search |
145
- | F5 | Sync |
146
-
147
- ## Installation
148
-
149
- ```bash
150
- npm install -g @bobfrankston/mailx
151
- mailx # Starts server + opens browser
152
- ```
153
-
154
- Or for development:
155
- ```bash
156
- git clone https://github.com/BobFrankston/mailx.git
157
- cd mailx
158
- npm install
159
- npm start # Starts server with --watch (auto-restart on changes)
160
- ```
157
+ | Escape | Clear search / Discard compose |
158
+ | F5 | Sync all folders |
161
159
 
162
- The native WebView2 app (optional):
163
- ```bash
164
- launch.ps1 # Builds and runs the Rust launcher
165
- launch.ps1 -restart # Kill existing server first
166
- ```
160
+ ### View Options (View menu)
161
+ - **Two-line view** -- compact message rows
162
+ - **Preview pane** -- toggle on/off
163
+ - **Flagged only** -- show only starred messages
167
164
 
168
165
  ## Data Storage
169
166
 
@@ -171,47 +168,47 @@ All data lives in `~/.mailx/` (e.g., `C:\Users\You\.mailx\`):
171
168
 
172
169
  | File | Shared? | Purpose |
173
170
  |------|---------|---------|
174
- | config.jsonc | No | Points to shared settings dir + local overrides |
171
+ | config.jsonc | No | Points to shared settings + local overrides |
172
+ | settings.jsonc | Yes | Account configs, sync, UI prefs (or split files below) |
175
173
  | accounts.jsonc | Yes | IMAP/SMTP account configs |
176
- | preferences.jsonc | Yes | UI, sync, font settings |
174
+ | preferences.jsonc | Yes | UI and sync settings |
177
175
  | allowlist.jsonc | Yes | Remote content sender/domain allow-list |
178
- | settings.jsonc | Yes | Legacy combined settings (still supported) |
179
- | mailx.db | No | SQLite — headers, contacts, sync state |
176
+ | mailx.db | No | SQLite metadata (headers, contacts, sync state) |
180
177
  | mailxstore/ | No | Cached message bodies (.eml per message) |
178
+ | logs/ | No | Server logs (auto-deleted after 7 days) |
181
179
  | window.json | No | Window position (per machine) |
182
- | mailx-YYYY-MM-DD.log | No | Server log (auto-deleted after 7 days) |
180
+ | webview2/ | No | WebView2 data (Windows native app) |
183
181
 
184
- **Shared** files can live on OneDrive/Dropbox — `config.jsonc` points to the shared directory. **Local** files stay on the machine.
182
+ **Shared** files can live on OneDrive/Dropbox. **Local** files stay on the machine.
185
183
 
186
184
  ### Safe to Delete
187
185
 
188
- - **mailxstore/** cached bodies, re-downloaded automatically during sync or on demand. Delete to reclaim space or reset the cache.
189
- - **mailx.db** re-created on next startup, triggers full re-sync of all messages within the history window.
190
- - **mailx-*.log** auto-cleaned after 7 days. Safe to delete anytime.
191
- - **window.json** resets window position to default 1280×800.
186
+ - **mailxstore/** -- re-downloaded during sync. Delete to reclaim space.
187
+ - **mailx.db** -- re-created on startup, triggers full re-sync.
188
+ - **logs/** -- safe to delete anytime.
189
+ - **window.json** -- resets window position.
192
190
 
193
- ### config.jsonc
194
-
195
- ```json
196
- {
197
- "sharedDir": "C:/Users/You/OneDrive/home/.mailx",
198
- "storePath": "C:/Users/You/.mailx/mailxstore",
199
- "historyDays": 90
200
- }
201
- ```
191
+ ## Development
202
192
 
203
- - **sharedDir** — directory containing shared settings files (accounts, preferences, allowlist)
204
- - **storePath** — where cached .eml files are stored
205
- - **historyDays** — per-machine override for sync history (shared default is 30)
193
+ ```bash
194
+ git clone https://github.com/BobFrankston/mailx.git
195
+ cd mailx
196
+ npm install
197
+ npm start # Server with --watch (auto-restart)
198
+ # Open http://127.0.0.1:9333
206
199
 
207
- If `config.jsonc` doesn't exist, all settings default to `~/.mailx/`.
200
+ launch.ps1 # Windows native WebView2 app
201
+ launch.ps1 -restart # Kill existing and restart
202
+ launch.ps1 -clear # Clear WebView2 cache
203
+ ```
208
204
 
209
205
  ## Architecture
210
206
 
211
- - **Local-first** changes update local DB immediately, background worker syncs to IMAP
212
- - **Offline reading** full message bodies cached during sync
213
- - **IMAP IDLE** instant new mail notifications + 30s poll fallback
214
- - **Outbox** queued in IMAP Outbox folder with multi-machine interlock via flags
215
- - **Remote content blocking** HTML sanitized server-side, CSP in iframe, per-sender/domain allow-list
216
- - **Search** SQLite FTS5 full-text index with qualifiers
217
- - **Unified mode** `mailx` command runs server in-process (no separate server). Use `--server` for standalone server mode.
207
+ - **Local-first** -- changes update local DB immediately, background worker syncs to IMAP
208
+ - **Offline reading** -- full message bodies cached as .eml files during sync
209
+ - **IMAP IDLE** -- instant new mail notifications + periodic sync fallback
210
+ - **Outbox** -- queued in IMAP Outbox folder with multi-machine interlock via flags
211
+ - **Remote content blocking** -- HTML sanitized server-side, CSP in iframe, per-sender/domain allow-list
212
+ - **Search** -- SQLite FTS5 full-text index, IMAP server search, regex filtering
213
+ - **Built-in SQLite** -- uses Node.js built-in node:sqlite (no native compilation needed)
214
+ - **Cross-platform** -- Windows (WebView2), Linux (webkit2gtk), any platform (browser mode)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.23",
3
+ "version": "1.0.24",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -48,6 +48,13 @@ const CLIENT_VERSION = clientPkg.version;
48
48
  // ── Initialize ──
49
49
  initLocalConfig();
50
50
  const settings = loadSettings();
51
+ if (settings.accounts.length === 0) {
52
+ console.log(" No accounts configured.");
53
+ console.log(" See README for setup: https://github.com/BobFrankston/mailx#first-time-setup");
54
+ console.log(" Quick: create ~/.mailx/settings.jsonc with your IMAP/SMTP account details.");
55
+ console.log(" Or: place shared settings on OneDrive at home/.mailx/settings.jsonc");
56
+ console.log(" Server will start at http://127.0.0.1:9333 — configure accounts to begin.");
57
+ }
51
58
  const dbDir = getConfigDir();
52
59
  const db = new MailxDB(dbDir);
53
60
  const imapManager = new ImapManager(db);
@@ -205,16 +205,48 @@ export function getConfigDir() {
205
205
  }
206
206
  /** Get the shared settings directory */
207
207
  export { getSharedDir };
208
+ /** Auto-detect shared settings on OneDrive or common cloud sync locations */
209
+ function detectSharedDir() {
210
+ const home = process.env.USERPROFILE || process.env.HOME || "";
211
+ const candidates = [
212
+ // OneDrive paths (Windows)
213
+ process.env.OneDrive && path.join(process.env.OneDrive, "home", ".mailx"),
214
+ process.env.OneDriveConsumer && path.join(process.env.OneDriveConsumer, "home", ".mailx"),
215
+ home && path.join(home, "OneDrive", "home", ".mailx"),
216
+ // Linux/Mac OneDrive
217
+ home && path.join(home, "OneDrive", "home", ".mailx"),
218
+ // Dropbox
219
+ home && path.join(home, "Dropbox", ".mailx"),
220
+ // Local fallback — just use ~/.mailx itself
221
+ ].filter(Boolean);
222
+ for (const dir of candidates) {
223
+ if (fs.existsSync(path.join(dir, "settings.jsonc")) || fs.existsSync(path.join(dir, "accounts.jsonc"))) {
224
+ return dir;
225
+ }
226
+ }
227
+ return undefined;
228
+ }
208
229
  /** Initialize local config if it doesn't exist */
209
230
  export function initLocalConfig(sharedDir, storePath) {
210
231
  if (fs.existsSync(LOCAL_CONFIG_PATH) && !sharedDir && !storePath)
211
232
  return;
212
233
  const existing = readLocalConfig();
234
+ // Auto-detect shared settings if not configured
235
+ let resolvedSharedDir = sharedDir || existing.sharedDir;
236
+ if (!resolvedSharedDir && existing.settingsPath) {
237
+ resolvedSharedDir = path.dirname(existing.settingsPath);
238
+ }
239
+ if (!resolvedSharedDir) {
240
+ resolvedSharedDir = detectSharedDir();
241
+ if (resolvedSharedDir)
242
+ console.log(` Auto-detected shared settings: ${resolvedSharedDir}`);
243
+ }
213
244
  const config = {
214
245
  ...existing,
215
- sharedDir: sharedDir || existing.sharedDir || existing.settingsPath ? path.dirname(existing.settingsPath) : undefined,
246
+ sharedDir: resolvedSharedDir,
216
247
  storePath: storePath || existing.storePath || DEFAULT_STORE_PATH,
217
248
  };
249
+ fs.mkdirSync(LOCAL_DIR, { recursive: true });
218
250
  atomicWrite(LOCAL_CONFIG_PATH, config);
219
251
  }
220
252
  const DEFAULT_SETTINGS = {