@bobfrankston/mailx 1.0.37 → 1.0.38

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
@@ -35,47 +35,76 @@ Once OneDrive syncs, mailx picks up your accounts automatically.
35
35
 
36
36
  (On Windows `~` means `%USERPROFILE%`, e.g., `C:\Users\You\.mailx\settings.jsonc`)
37
37
 
38
+ **Gmail -- minimal config (just your email):**
39
+ ```jsonc
40
+ {
41
+ "accounts": [
42
+ { "email": "you@gmail.com" }
43
+ ]
44
+ }
45
+ ```
46
+
47
+ mailx auto-fills Gmail's IMAP/SMTP/OAuth settings. First run opens a browser for OAuth consent.
48
+
49
+ **Standard IMAP -- just host and password:**
38
50
  ```jsonc
39
51
  {
40
52
  "accounts": [
41
53
  {
42
- "id": "mymail", // Internal ID (no spaces, lowercase)
43
- "name": "Your Name", // Sender name in From: header
44
- "label": "Work", // Display label in UI (optional, defaults to name)
45
- "email": "you@example.com", // Your email address
54
+ "email": "you@example.com",
55
+ "password": "your-password",
56
+ "imap": { "host": "imap.example.com" },
57
+ "smtp": { "host": "smtp.example.com" }
58
+ }
59
+ ]
60
+ }
61
+ ```
62
+
63
+ Defaults: port 993/587, TLS on, auth password, username = email address. Only provide fields that differ from defaults.
64
+
65
+ **Full config with all optional fields:**
66
+ ```jsonc
67
+ {
68
+ "accounts": [
69
+ {
70
+ "id": "mymail", // Internal ID (default: domain name)
71
+ "name": "Your Name", // From: header name (default: local part of email)
72
+ "label": "Work", // UI display label (default: name)
73
+ "email": "you@example.com",
46
74
  "imap": {
47
- "host": "imap.example.com", // IMAP server
48
- "port": 993, // Usually 993 for SSL/TLS
49
- "tls": true,
50
- "auth": "password", // "password" or "oauth2" (Gmail)
51
- "user": "you@example.com", // IMAP username
52
- "password": "your-password" // IMAP password (not needed for oauth2)
75
+ "host": "imap.example.com", // Default: imap.{domain}
76
+ "port": 993, // Default: 993
77
+ "tls": true, // Default: true
78
+ "auth": "password", // Default: "password" (or "oauth2" for Gmail/Outlook)
79
+ "user": "you@example.com", // Default: email address
80
+ "password": "your-password"
53
81
  },
54
82
  "smtp": {
55
- "host": "smtp.example.com", // SMTP server
56
- "port": 587, // Usually 587 for STARTTLS
57
- "tls": true,
83
+ "host": "smtp.example.com", // Default: smtp.{domain}
84
+ "port": 587, // Default: 587
85
+ "tls": true, // Default: true
58
86
  "auth": "password",
59
87
  "user": "you@example.com",
60
88
  "password": "your-password"
61
89
  },
62
- "enabled": true,
63
- "defaultSend": true, // Use this account for sending when From doesn't match
64
- "relayDomains": [], // Domains to skip in Delivered-To (optional)
65
- "deliveredToPrefix": [] // Prefixes to strip from Delivered-To alias (optional)
90
+ "enabled": true, // Default: true
91
+ "defaultSend": true, // Use this account when From doesn't match
92
+ "relayDomains": [], // Domains to skip in Delivered-To chain
93
+ "deliveredToPrefix": [] // Prefixes to strip from Delivered-To alias
66
94
  }
67
95
  ],
68
96
  "sync": {
69
- "intervalMinutes": 5, // Full sync interval (minutes)
70
- "historyDays": 0 // Days of history to sync (0 = all)
97
+ "intervalMinutes": 5, // Default: 5
98
+ "historyDays": 0 // Default: 30 (0 = all)
71
99
  },
72
100
  "ui": {
73
- "theme": "system", // "system", "dark", or "light"
74
- "fontSize": 15
101
+ "theme": "system" // "system" (default), "dark", or "light"
75
102
  }
76
103
  }
77
104
  ```
78
105
 
106
+ **Known providers with automatic defaults:** Gmail, Google, Outlook, Hotmail, Yahoo, iCloud. For these, only `email` is required.
107
+
79
108
  No `config.jsonc` needed -- when it doesn't exist, mailx reads settings directly from `~/.mailx/`.
80
109
 
81
110
  **Step 2.** Run `mailx` and open `http://127.0.0.1:9333` in your browser.
@@ -112,28 +141,15 @@ move "%USERPROFILE%\.mailx\settings.jsonc" "%OneDrive%\home\.mailx\settings.json
112
141
  - `historyDays` in `config.jsonc` overrides the shared default per machine
113
142
  - On new machines, if `config.jsonc` doesn't exist, mailx auto-detects OneDrive at `%OneDrive%/home/.mailx/`
114
143
 
115
- ### Adding a Gmail account
144
+ ### Gmail OAuth setup
116
145
 
117
- Gmail requires OAuth2 instead of a password:
146
+ Gmail accounts auto-configure -- just add `{ "email": "you@gmail.com" }` to accounts. But OAuth requires a one-time Google Cloud setup:
118
147
 
119
148
  1. Go to [Google Cloud Console](https://console.cloud.google.com/) and create a project
120
149
  2. Enable the **Gmail API** (and **People API** for contacts)
121
150
  3. Create **OAuth 2.0 credentials** (Desktop app type)
122
151
  4. Download `credentials.json` to the iflow package directory
123
- 5. Add the Gmail account to `settings.jsonc` with `"auth": "oauth2"`
124
- 6. First connection opens a browser for OAuth consent. Tokens are cached and refresh automatically.
125
-
126
- ```jsonc
127
- {
128
- "id": "gmail",
129
- "name": "Your Name",
130
- "label": "Gmail",
131
- "email": "you@gmail.com",
132
- "imap": { "host": "imap.gmail.com", "port": 993, "tls": true, "auth": "oauth2", "user": "you@gmail.com" },
133
- "smtp": { "host": "smtp.gmail.com", "port": 587, "tls": true, "auth": "oauth2", "user": "you@gmail.com" },
134
- "enabled": true
135
- }
136
- ```
152
+ 5. First connection opens a browser for OAuth consent. Tokens are cached and refresh automatically.
137
153
 
138
154
  ## Usage
139
155
 
package/bin/mailx.js CHANGED
@@ -172,16 +172,34 @@ async function main() {
172
172
 
173
173
  let launcherPath = launcherPaths.find(p => fs.existsSync(p));
174
174
 
175
+ // On Linux, skip native launcher if no display server available
176
+ if (launcherPath && process.platform === "linux" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
177
+ log("No display server (DISPLAY/WAYLAND_DISPLAY not set) — skipping native launcher");
178
+ launcherPath = undefined;
179
+ }
180
+
175
181
  if (launcherPath) {
176
182
  console.log("Starting mailx...");
177
183
  log(`Launching: ${launcherPath}`);
178
184
  const { spawn } = await import("node:child_process");
179
- const child = spawn(launcherPath, args, { detached: true, stdio: "ignore" });
180
- child.unref();
181
- console.log("mailx launched");
185
+ try {
186
+ const child = spawn(launcherPath, args, { detached: true, stdio: "ignore" });
187
+ child.on("error", () => {
188
+ console.log("Native launcher failed, starting in browser mode...");
189
+ process.argv.push("--server");
190
+ main();
191
+ });
192
+ child.unref();
193
+ console.log("mailx launched");
194
+ } catch (e) {
195
+ console.log(`Native launcher failed: ${e.message}`);
196
+ console.log("Starting in browser mode...");
197
+ process.argv.push("--server");
198
+ await main();
199
+ }
182
200
  } else {
183
- console.log("Native launcher not found, starting in browser mode...");
184
- log("Falling back to --server mode");
201
+ console.log("Starting in browser mode...");
202
+ log("No native launcher — falling back to --server mode");
185
203
  process.argv.push("--server");
186
204
  await main(); // recurse with --server
187
205
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.37",
3
+ "version": "1.0.38",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -20,7 +20,7 @@
20
20
  "postinstall": "node launcher/builder/postinstall.js"
21
21
  },
22
22
  "dependencies": {
23
- "@bobfrankston/iflow": "^1.0.8",
23
+ "@bobfrankston/iflow": "^1.0.9",
24
24
  "@bobfrankston/miscinfo": "^1.0.6",
25
25
  "@bobfrankston/oauthsupport": "^1.0.11",
26
26
  "@bobfrankston/rust-builder": "^0.1.2",
@@ -105,6 +105,65 @@ function saveFile(filename, data) {
105
105
  catch { /* ignore */ }
106
106
  }
107
107
  }
108
+ const PROVIDERS = {
109
+ "gmail.com": {
110
+ imap: { host: "imap.gmail.com", port: 993, tls: true, auth: "oauth2" },
111
+ smtp: { host: "smtp.gmail.com", port: 587, tls: true, auth: "oauth2" },
112
+ },
113
+ "googlemail.com": {
114
+ imap: { host: "imap.gmail.com", port: 993, tls: true, auth: "oauth2" },
115
+ smtp: { host: "smtp.gmail.com", port: 587, tls: true, auth: "oauth2" },
116
+ },
117
+ "outlook.com": {
118
+ imap: { host: "outlook.office365.com", port: 993, tls: true, auth: "oauth2" },
119
+ smtp: { host: "smtp.office365.com", port: 587, tls: true, auth: "oauth2" },
120
+ },
121
+ "hotmail.com": {
122
+ imap: { host: "outlook.office365.com", port: 993, tls: true, auth: "oauth2" },
123
+ smtp: { host: "smtp.office365.com", port: 587, tls: true, auth: "oauth2" },
124
+ },
125
+ "yahoo.com": {
126
+ imap: { host: "imap.mail.yahoo.com", port: 993, tls: true, auth: "password" },
127
+ smtp: { host: "smtp.mail.yahoo.com", port: 587, tls: true, auth: "password" },
128
+ },
129
+ "icloud.com": {
130
+ imap: { host: "imap.mail.me.com", port: 993, tls: true, auth: "password" },
131
+ smtp: { host: "smtp.mail.me.com", port: 587, tls: true, auth: "password" },
132
+ },
133
+ };
134
+ /** Fill in provider defaults for an account based on email domain */
135
+ function normalizeAccount(acct, globalName) {
136
+ const email = acct.email || "";
137
+ const domain = email.split("@")[1]?.toLowerCase() || "";
138
+ const provider = PROVIDERS[domain];
139
+ const user = acct.imap?.user || acct.user || email;
140
+ return {
141
+ id: acct.id || domain.split(".")[0] || "account",
142
+ name: acct.name || globalName || email.split("@")[0],
143
+ label: acct.label,
144
+ email,
145
+ imap: {
146
+ host: acct.imap?.host || provider?.imap.host || `imap.${domain}`,
147
+ port: acct.imap?.port || provider?.imap.port || 993,
148
+ tls: acct.imap?.tls ?? provider?.imap.tls ?? true,
149
+ auth: acct.imap?.auth || provider?.imap.auth || "password",
150
+ user: acct.imap?.user || user,
151
+ password: acct.imap?.password || acct.password,
152
+ },
153
+ smtp: {
154
+ host: acct.smtp?.host || provider?.smtp.host || `smtp.${domain}`,
155
+ port: acct.smtp?.port || provider?.smtp.port || 587,
156
+ tls: acct.smtp?.tls ?? provider?.smtp.tls ?? true,
157
+ auth: acct.smtp?.auth || provider?.smtp.auth || "password",
158
+ user: acct.smtp?.user || user,
159
+ password: acct.smtp?.password || acct.password,
160
+ },
161
+ enabled: acct.enabled ?? true,
162
+ defaultSend: acct.defaultSend,
163
+ relayDomains: acct.relayDomains,
164
+ deliveredToPrefix: acct.deliveredToPrefix,
165
+ };
166
+ }
108
167
  // ── Defaults ──
109
168
  const DEFAULT_ACCOUNTS = [];
110
169
  const DEFAULT_PREFERENCES = {
@@ -143,12 +202,14 @@ export function loadAccounts() {
143
202
  }
144
203
  catch { /* ignore */ }
145
204
  }
146
- return accounts.accounts || accounts;
205
+ const raw = accounts.accounts || accounts;
206
+ const globalName = accounts.name || "";
207
+ return raw.map((a) => normalizeAccount(a, globalName));
147
208
  }
148
209
  // Legacy: read from settings.jsonc
149
210
  const legacy = loadLegacySettings();
150
211
  if (legacy?.accounts)
151
- return legacy.accounts;
212
+ return legacy.accounts.map((a) => normalizeAccount(a, legacy.name));
152
213
  return DEFAULT_ACCOUNTS;
153
214
  }
154
215
  /** Save account configs */