@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 +53 -37
- package/bin/mailx.js +23 -5
- package/package.json +2 -2
- package/packages/mailx-settings/index.js +63 -2
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
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
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", //
|
|
48
|
-
"port": 993, //
|
|
49
|
-
"tls": true,
|
|
50
|
-
"auth": "password", // "password" or "oauth2"
|
|
51
|
-
"user": "you@example.com", //
|
|
52
|
-
"password": "your-password"
|
|
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", //
|
|
56
|
-
"port": 587, //
|
|
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
|
|
64
|
-
"relayDomains": [], // Domains to skip in Delivered-To
|
|
65
|
-
"deliveredToPrefix": [] // Prefixes to strip from Delivered-To alias
|
|
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, //
|
|
70
|
-
"historyDays": 0 //
|
|
97
|
+
"intervalMinutes": 5, // Default: 5
|
|
98
|
+
"historyDays": 0 // Default: 30 (0 = all)
|
|
71
99
|
},
|
|
72
100
|
"ui": {
|
|
73
|
-
"theme": "system"
|
|
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
|
-
###
|
|
144
|
+
### Gmail OAuth setup
|
|
116
145
|
|
|
117
|
-
Gmail
|
|
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.
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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("
|
|
184
|
-
log("
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 */
|