@bobfrankston/mailx 1.0.174 → 1.0.175
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.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"height":1047,"width":1844,"x":664,"y":330}
|
package/client/compose/editor.js
CHANGED
|
@@ -8,9 +8,12 @@ function createQuillEditor(container) {
|
|
|
8
8
|
placeholder: "Write your message...",
|
|
9
9
|
modules: {
|
|
10
10
|
toolbar: [
|
|
11
|
+
[{ font: [] }, { size: ["small", false, "large", "huge"] }],
|
|
11
12
|
[{ header: [1, 2, 3, false] }],
|
|
12
13
|
["bold", "italic", "underline", "strike"],
|
|
14
|
+
[{ color: [] }, { background: [] }],
|
|
13
15
|
[{ list: "ordered" }, { list: "bullet" }],
|
|
16
|
+
[{ align: [] }],
|
|
14
17
|
["blockquote", "link", "image"],
|
|
15
18
|
["clean"]
|
|
16
19
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.175",
|
|
4
4
|
"description": "Local-first email client with IMAP sync and standalone native app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/mailx.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@bobfrankston/iflow-node": "^0.1.1",
|
|
25
25
|
"@bobfrankston/miscinfo": "^1.0.7",
|
|
26
26
|
"@bobfrankston/oauthsupport": "^1.0.20",
|
|
27
|
-
"@bobfrankston/msger": "^0.1.
|
|
27
|
+
"@bobfrankston/msger": "^0.1.224",
|
|
28
28
|
"@capacitor/android": "^8.3.0",
|
|
29
29
|
"@capacitor/cli": "^8.3.0",
|
|
30
30
|
"@capacitor/core": "^8.3.0",
|
|
@@ -184,6 +184,8 @@ export declare class ImapManager extends EventEmitter {
|
|
|
184
184
|
queueOutgoing(accountId: string, rawMessage: string | Buffer): Promise<void>;
|
|
185
185
|
/** Process local file queue — move to IMAP Outbox when server is reachable */
|
|
186
186
|
private processLocalQueue;
|
|
187
|
+
/** Send a raw RFC 2822 message via SMTP for a given account */
|
|
188
|
+
private sendRawViaSMTP;
|
|
187
189
|
/** Process Outbox — send pending messages with flag-based interlock */
|
|
188
190
|
processOutbox(accountId: string): Promise<void>;
|
|
189
191
|
/** Start background Outbox worker — runs immediately then every 10 seconds */
|
|
@@ -1681,6 +1681,23 @@ export class ImapManager extends EventEmitter {
|
|
|
1681
1681
|
const files = fs.readdirSync(localQueue).filter(f => f.endsWith(".ltr"));
|
|
1682
1682
|
if (files.length === 0)
|
|
1683
1683
|
return;
|
|
1684
|
+
// Gmail/API accounts: send directly via SMTP from local queue (no IMAP outbox)
|
|
1685
|
+
if (this.isGmailAccount(accountId)) {
|
|
1686
|
+
for (const file of files) {
|
|
1687
|
+
const filePath = path.join(localQueue, file);
|
|
1688
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
1689
|
+
try {
|
|
1690
|
+
await this.sendRawViaSMTP(accountId, raw);
|
|
1691
|
+
fs.unlinkSync(filePath);
|
|
1692
|
+
console.log(` [outbox] Sent local ${file} via SMTP`);
|
|
1693
|
+
}
|
|
1694
|
+
catch (e) {
|
|
1695
|
+
console.error(` [outbox] Send failed for ${file}: ${e.message}`);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
// IMAP accounts: append to IMAP Outbox for multi-machine interlock
|
|
1684
1701
|
try {
|
|
1685
1702
|
const outboxPath = await this.ensureOutbox(accountId);
|
|
1686
1703
|
const client = await this.createClientWithLimit(accountId);
|
|
@@ -1704,6 +1721,48 @@ export class ImapManager extends EventEmitter {
|
|
|
1704
1721
|
// IMAP still unreachable — leave files for next attempt
|
|
1705
1722
|
}
|
|
1706
1723
|
}
|
|
1724
|
+
/** Send a raw RFC 2822 message via SMTP for a given account */
|
|
1725
|
+
async sendRawViaSMTP(accountId, raw) {
|
|
1726
|
+
const settings = loadSettings();
|
|
1727
|
+
const account = settings.accounts.find(a => a.id === accountId);
|
|
1728
|
+
if (!account?.smtp)
|
|
1729
|
+
throw new Error(`No SMTP config for ${accountId}`);
|
|
1730
|
+
let smtpAuth;
|
|
1731
|
+
if (account.smtp.auth === "password") {
|
|
1732
|
+
smtpAuth = { user: account.smtp.user, pass: account.smtp.password };
|
|
1733
|
+
}
|
|
1734
|
+
else if (account.smtp.auth === "oauth2") {
|
|
1735
|
+
const accessToken = await this.getOAuthToken(accountId);
|
|
1736
|
+
if (!accessToken)
|
|
1737
|
+
throw new Error("OAuth token not available");
|
|
1738
|
+
smtpAuth = { type: "OAuth2", user: account.smtp.user, accessToken };
|
|
1739
|
+
}
|
|
1740
|
+
const { createTransport } = await import("nodemailer");
|
|
1741
|
+
const transport = createTransport({
|
|
1742
|
+
host: account.smtp.host,
|
|
1743
|
+
port: account.smtp.port,
|
|
1744
|
+
secure: account.smtp.port === 465,
|
|
1745
|
+
auth: smtpAuth,
|
|
1746
|
+
tls: { rejectUnauthorized: false },
|
|
1747
|
+
});
|
|
1748
|
+
const parseAddrs = (s) => s.match(/[\w.+-]+@[\w.-]+/g) || [];
|
|
1749
|
+
const toMatch = raw.match(/^To:\s*(.+)$/mi);
|
|
1750
|
+
const ccMatch = raw.match(/^Cc:\s*(.+)$/mi);
|
|
1751
|
+
const bccMatch = raw.match(/^Bcc:\s*(.+)$/mi);
|
|
1752
|
+
const fromMatch = raw.match(/^From:\s*(.+)$/mi);
|
|
1753
|
+
const recipients = [
|
|
1754
|
+
...(toMatch ? parseAddrs(toMatch[1]) : []),
|
|
1755
|
+
...(ccMatch ? parseAddrs(ccMatch[1]) : []),
|
|
1756
|
+
...(bccMatch ? parseAddrs(bccMatch[1]) : []),
|
|
1757
|
+
];
|
|
1758
|
+
const sender = fromMatch ? (parseAddrs(fromMatch[1])[0] || account.email) : account.email;
|
|
1759
|
+
if (recipients.length === 0)
|
|
1760
|
+
throw new Error("No recipients");
|
|
1761
|
+
const rawToSend = raw.replace(/^Bcc:.*\r?\n/mi, "");
|
|
1762
|
+
this.saveSendingCopy(accountId, rawToSend, "sent");
|
|
1763
|
+
await transport.sendMail({ envelope: { from: sender, to: recipients }, raw: rawToSend });
|
|
1764
|
+
console.log(` [smtp] ${accountId}: sent to ${recipients.join(", ")}`);
|
|
1765
|
+
}
|
|
1707
1766
|
/** Process Outbox — send pending messages with flag-based interlock */
|
|
1708
1767
|
async processOutbox(accountId) {
|
|
1709
1768
|
const outboxFolder = this.findFolder(accountId, "outbox");
|
|
@@ -1712,7 +1771,7 @@ export class ImapManager extends EventEmitter {
|
|
|
1712
1771
|
// Skip if this account's sync is failing — don't pile up connections
|
|
1713
1772
|
if (this.connectionBackoff.has(accountId) && Date.now() < (this.connectionBackoff.get(accountId) || 0))
|
|
1714
1773
|
return;
|
|
1715
|
-
// Gmail
|
|
1774
|
+
// Gmail: skip IMAP outbox check — sending handled by processLocalQueue which sends directly via SMTP
|
|
1716
1775
|
if (this.isGmailAccount(accountId))
|
|
1717
1776
|
return;
|
|
1718
1777
|
const settings = loadSettings();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
From: Bob Frankston <Bob.Frankston@Gmail.com>
|
|
2
|
+
To: David P. Reed <dpreed@deepplum.com>
|
|
3
|
+
Cc: Brian DeLacey <bdelacey@gmail.com>
|
|
4
|
+
Bcc: bob@bob.ma
|
|
5
|
+
Subject: Re: Converting POPFile from perl to Python
|
|
6
|
+
Date: Wed, 08 Apr 2026 01:02:13 GMT
|
|
7
|
+
Message-ID: <1775610133517.ug0o0gii4wm@Gmail.com>
|
|
8
|
+
MIME-Version: 1.0
|
|
9
|
+
Content-Type: text/html; charset=UTF-8
|
|
10
|
+
Content-Transfer-Encoding: base64
|
|
11
|
+
|
|
12
|
+
PHA+SSdtIHJlc3BvbmRpbmcgdXNpbmcgbXkgZW1haWwgcHJvZ3JhbS48L3A+PHA+PGJyPjwvcD48
|
|
13
|
+
cD5UaGVyZSBpcyBzbyBtdWNoIHRvIGNhdGNoIHVwIHdpdGggLS0gdGhlIGdvb2QgYW5kIHRoZSBi
|
|
14
|
+
YWQuIE9uZSBvYnNlcnZhdGlvbiBpcyB0aGF0IGl0IGxlYXJuZWQgdG8gbXVjaCB0byBwcm9ncmFt
|
|
15
|
+
IGxpa2UgYSBodW1hbiBhbmQgbm90IGVub3VnaC4gT25lIGh1bW9yb3VzIHRoaW5nIGlzIHdoZW4g
|
|
16
|
+
aXQgZXN0aW1hdGVzIGEgdGFzayB3aWxsIHRha2UgYSB3ZWVrIGJ1dCBpdCBkb2VzIGl0IGluIGZp
|
|
17
|
+
dmUgbWludXRlcyBiZWNhdXNlIGl0IGlzIHVzaW5nIHN0YW5kYXJkIG1lYXN1cmVzLiBJdCdzIGRl
|
|
18
|
+
c2lnbiBzZW5zZSBpcyBmdXJzaGl0IGJ1dCBpZiB5b3Ugd29yayB3aXRoIGl0IGFuZCBnZXQgcGFz
|
|
19
|
+
dCB0aGUgSmVuZ2EgbW9kZSB5b3UgY2FuIGdldCBsb3RzIGRvbmUuIEZvciBleGFtcGxlLCBpbWFw
|
|
20
|
+
IGRvZXNuJ3Qgd29yayB3ZWxsIHdpdGggZ21haWwgYnV0IGl0IHNhaWQgdGhhdCBhZGRpbmcgZ21h
|
|
21
|
+
aWwgd2FzIGEgbWFqb3IgcHJvamVjdCBidXQgSSBzYWlkIGRvIGl0IGFueXdheSBhbmQgZml2ZSBt
|
|
22
|
+
aW51dGVzIGxhdGVyIGl0IHdhcyBkb25lLiBucG0gaW5zdGFsbCAtZyBAYm9iZnJhbmtzdG9uL21h
|
|
23
|
+
aWx4IGlzbid0IHF1aXQgcmVhZHkgZm9yIHByaW1lIHRpbWUuPC9wPjxwPjxicj48L3A+PHA+U3Rp
|
|
24
|
+
bGwgYSBodWdlIHRvZG8gbGlzdCAoaW4gdGhlIG1haWx4IGRpciBpZiB5b3UncmUgY3VyaW91cy4g
|
|
25
|
+
SXQgd2lsbCB3YW50IHRvIHN0YXJ0IHdpdGggYSBnbWFpbCBhZGRyZXNzIGFuZCBjcmVhdGVzIGEg
|
|
26
|
+
bWFpbHggZGlyZWN0b3J5IG9uIGdkcml2ZS4gQSBnYWluLCBzdGlsbCBsb3RzIG9mIGlzc3VlcyB3
|
|
27
|
+
aGljaCB5b3UgY2FuIHJlcG9ydCB0byBtZSBidXQgaXQgaXMgY29taW5nIHRvZ2V0aGVyLjwvcD48
|
|
28
|
+
cD48YnI+PC9wPjxwPk9uIDQvNy8yMDI2LCA1OjA0OjU3IFBNLCBEYXZpZCBQLiBSZWVkICZsdDtk
|
|
29
|
+
cHJlZWRAZGVlcHBsdW0uY29tJmd0OyB3cm90ZTo8L3A+PGRpdiBjbGFzcz0icWwtY29kZS1ibG9j
|
|
30
|
+
ay1jb250YWluZXIiIHNwZWxsY2hlY2s9ImZhbHNlIj48ZGl2IGNsYXNzPSJxbC1jb2RlLWJsb2Nr
|
|
31
|
+
IiBkYXRhLWxhbmd1YWdlPSJwbGFpbiI+Rmlyc3Qgb3ZlcmFsbCB0ZXN0IGJlZ2FuIGxvb3Bpbmcg
|
|
32
|
+
Zm9yZXZlci4gSSBzdG9wcGVkIGl0LCBhbmQgaW5xdWlyZWQgd2hhdCB0aGUgaGVsbCwgYW5kPC9k
|
|
33
|
+
aXY+PGRpdiBjbGFzcz0icWwtY29kZS1ibG9jayIgZGF0YS1sYW5ndWFnZT0icGxhaW4iPmhlcmUg
|
|
34
|
+
aXMgd2hhdCBpdCBzYXlzLiBHYWFoLiBUaGlzIGlzIG5vdCBleHBsYWluaW5nIG11Y2gsIGFuZCBp
|
|
35
|
+
dCB3cm90ZSB0aGlzIGNvZGUuLi48L2Rpdj48ZGl2IGNsYXNzPSJxbC1jb2RlLWJsb2NrIiBkYXRh
|
|
36
|
+
LWxhbmd1YWdlPSJwbGFpbiI+PGJyPjwvZGl2PjxkaXYgY2xhc3M9InFsLWNvZGUtYmxvY2siIGRh
|
|
37
|
+
dGEtbGFuZ3VhZ2U9InBsYWluIj4tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTwvZGl2Pjxk
|
|
38
|
+
aXYgY2xhc3M9InFsLWNvZGUtYmxvY2siIGRhdGEtbGFuZ3VhZ2U9InBsYWluIj5UaGUgdGVzdCBo
|
|
39
|
+
YW5ncyBiZWNhdXNlICNoYW5kbGVDbGllbnQgaW4gUE9QM1Byb3h5IHRocm93cyBiZWZvcmUgc2Vu
|
|
40
|
+
ZGluZyB0aGUgYmFubmVyLiBUaGUgY3VscHJpdCBpcyB0aGlzIGRlYWQgY29kZTo8L2Rpdj48ZGl2
|
|
41
|
+
IGNsYXNzPSJxbC1jb2RlLWJsb2NrIiBkYXRhLWxhbmd1YWdlPSJwbGFpbiI+PGJyPjwvZGl2Pjxk
|
|
42
|
+
aXYgY2xhc3M9InFsLWNvZGUtYmxvY2siIGRhdGEtbGFuZ3VhZ2U9InBsYWluIj4gIGNvbnN0IGNs
|
|
43
|
+
aWVudFdyaXRlciA9IG5ldyBXcml0YWJsZVN0cmVhbURlZmF1bHRXcml0ZXIoPC9kaXY+PGRpdiBj
|
|
44
|
+
bGFzcz0icWwtY29kZS1ibG9jayIgZGF0YS1sYW5ndWFnZT0icGxhaW4iPiAgICBjbGllbnQud3Jp
|
|
45
|
+
dGFibGUuZ2V0V3JpdGVyKCkucmVsZWFzZUxvY2soKSBhcyB1bmtub3duIGFzIFdyaXRhYmxlU3Ry
|
|
46
|
+
ZWFtPC9kaXY+PGRpdiBjbGFzcz0icWwtY29kZS1ibG9jayIgZGF0YS1sYW5ndWFnZT0icGxhaW4i
|
|
47
|
+
PiAgKTs8L2Rpdj48ZGl2IGNsYXNzPSJxbC1jb2RlLWJsb2NrIiBkYXRhLWxhbmd1YWdlPSJwbGFp
|
|
48
|
+
biI+PGJyPjwvZGl2PjxkaXYgY2xhc3M9InFsLWNvZGUtYmxvY2siIGRhdGEtbGFuZ3VhZ2U9InBs
|
|
49
|
+
YWluIj4gIHJlbGVhc2VMb2NrKCkgcmV0dXJucyB2b2lkLiBQYXNzaW5nIHZvaWQgdG8gV3JpdGFi
|
|
50
|
+
bGVTdHJlYW1EZWZhdWx0V3JpdGVyIHRocm93cyBhIFR5cGVFcnJvciwgd2hpY2ggaXMgc3dhbGxv
|
|
51
|
+
d2VkIGJ5IHRoZTwvZGl2PjxkaXYgY2xhc3M9InFsLWNvZGUtYmxvY2siIGRhdGEtbGFuZ3VhZ2U9
|
|
52
|
+
InBsYWluIj4gIGFjY2VwdCBsb29wJ3MgLmNhdGNoIOKAlCBzbyB0aGUgY2xpZW50IG5ldmVyIHJl
|
|
53
|
+
Y2VpdmVzIHRoZSBiYW5uZXIgYW5kIHdhaXRzIGZvcmV2ZXIuIGNsaWVudFdyaXRlciBpcyBhbHNv
|
|
54
|
+
IG5ldmVyIHVzZWQuPC9kaXY+PGRpdiBjbGFzcz0icWwtY29kZS1ibG9jayIgZGF0YS1sYW5ndWFn
|
|
55
|
+
ZT0icGxhaW4iPiAgRml4OjwvZGl2PjxkaXYgY2xhc3M9InFsLWNvZGUtYmxvY2siIGRhdGEtbGFu
|
|
56
|
+
Z3VhZ2U9InBsYWluIj48YnI+PC9kaXY+PGRpdiBjbGFzcz0icWwtY29kZS1ibG9jayIgZGF0YS1s
|
|
57
|
+
YW5ndWFnZT0icGxhaW4iPjxicj48L2Rpdj48ZGl2IGNsYXNzPSJxbC1jb2RlLWJsb2NrIiBkYXRh
|
|
58
|
+
LWxhbmd1YWdlPSJwbGFpbiI+PGJyPjwvZGl2PjwvZGl2Pg==
|