@bobfrankston/mailx 1.0.175 → 1.0.176

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.
@@ -1 +1 @@
1
- {"height":1047,"width":1844,"x":664,"y":330}
1
+ {"height":1047,"width":1844,"x":531,"y":264}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.175",
3
+ "version": "1.0.176",
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.224",
27
+ "@bobfrankston/msger": "^0.1.225",
28
28
  "@capacitor/android": "^8.3.0",
29
29
  "@capacitor/cli": "^8.3.0",
30
30
  "@capacitor/core": "^8.3.0",
@@ -1624,7 +1624,7 @@ export class ImapManager extends EventEmitter {
1624
1624
  /** Save a debug copy of outgoing mail to the sending directory */
1625
1625
  saveSendingCopy(accountId, rawMessage, label) {
1626
1626
  try {
1627
- const sendingDir = path.join(import.meta.dirname, "..", "..", "sending", accountId);
1627
+ const sendingDir = path.join(getConfigDir(), "sending", accountId);
1628
1628
  fs.mkdirSync(sendingDir, { recursive: true });
1629
1629
  const now = new Date();
1630
1630
  const pad2 = (n) => String(n).padStart(2, "0");
@@ -1665,7 +1665,7 @@ export class ImapManager extends EventEmitter {
1665
1665
  console.error(` [outbox] IMAP queue failed: ${e.message} — saving locally`);
1666
1666
  }
1667
1667
  // Fallback: save to local file queue
1668
- const localQueue = path.join(import.meta.dirname, "..", "..", "outbox", accountId);
1668
+ const localQueue = path.join(getConfigDir(), "outbox", accountId);
1669
1669
  fs.mkdirSync(localQueue, { recursive: true });
1670
1670
  const now = new Date();
1671
1671
  const pad2 = (n) => String(n).padStart(2, "0");
@@ -1675,7 +1675,7 @@ export class ImapManager extends EventEmitter {
1675
1675
  }
1676
1676
  /** Process local file queue — move to IMAP Outbox when server is reachable */
1677
1677
  async processLocalQueue(accountId) {
1678
- const localQueue = path.join(import.meta.dirname, "..", "..", "outbox", accountId);
1678
+ const localQueue = path.join(getConfigDir(), "outbox", accountId);
1679
1679
  if (!fs.existsSync(localQueue))
1680
1680
  return;
1681
1681
  const files = fs.readdirSync(localQueue).filter(f => f.endsWith(".ltr"));
@@ -6,6 +6,42 @@
6
6
  import * as dns from "node:dns/promises";
7
7
  import { loadSettings, saveSettings, loadAccounts, loadAccountsAsync, saveAccounts, initCloudConfig, loadAllowlist, saveAllowlist, loadAutocomplete, saveAutocomplete, getStorePath, getStorageInfo } from "@bobfrankston/mailx-settings";
8
8
  import { simpleParser } from "mailparser";
9
+ // ── Quoted-printable encoding (readable in debug .eml files) ──
10
+ function encodeQuotedPrintable(text) {
11
+ const bytes = Buffer.from(text, "utf-8");
12
+ let line = "";
13
+ let result = "";
14
+ for (let i = 0; i < bytes.length; i++) {
15
+ const b = bytes[i];
16
+ let encoded;
17
+ if (b === 0x0D && bytes[i + 1] === 0x0A) {
18
+ // CRLF — output as-is
19
+ result += line + "\r\n";
20
+ line = "";
21
+ i++; // skip LF
22
+ continue;
23
+ }
24
+ else if (b === 0x0A) {
25
+ // Bare LF — normalize to CRLF
26
+ result += line + "\r\n";
27
+ line = "";
28
+ continue;
29
+ }
30
+ else if ((b >= 33 && b <= 126 && b !== 61) || b === 9 || b === 32) {
31
+ encoded = String.fromCharCode(b);
32
+ }
33
+ else {
34
+ encoded = "=" + b.toString(16).toUpperCase().padStart(2, "0");
35
+ }
36
+ if (line.length + encoded.length > 75) {
37
+ result += line + "=\r\n";
38
+ line = "";
39
+ }
40
+ line += encoded;
41
+ }
42
+ result += line;
43
+ return result;
44
+ }
9
45
  // ── Email provider detection (MX-based) ──
10
46
  const GOOGLE_DOMAINS = ["gmail.com", "googlemail.com"];
11
47
  const MS_DOMAINS = ["outlook.com", "hotmail.com", "live.com"];
@@ -308,7 +344,7 @@ export class MailxService {
308
344
  const cc = msg.cc?.map((a) => a.name ? `${a.name} <${a.address}>` : a.address).join(", ");
309
345
  const bcc = msg.bcc?.map((a) => a.name ? `${a.name} <${a.address}>` : a.address).join(", ");
310
346
  const body = msg.bodyHtml || msg.bodyText || "";
311
- const bodyBase64 = Buffer.from(body, "utf-8").toString("base64").replace(/(.{76})/g, "$1\r\n");
347
+ const bodyEncoded = encodeQuotedPrintable(body);
312
348
  // Generate a unique Message-ID (required for threading, dedup, and RFC compliance)
313
349
  const domain = account.email.split("@")[1] || "mailx.local";
314
350
  const messageId = `<${Date.now()}.${Math.random().toString(36).slice(2)}@${domain}>`;
@@ -319,9 +355,9 @@ export class MailxService {
319
355
  `Message-ID: ${messageId}`,
320
356
  msg.inReplyTo ? `In-Reply-To: ${msg.inReplyTo}` : null,
321
357
  msg.references?.length ? `References: ${msg.references.join(" ")}` : null,
322
- `MIME-Version: 1.0`, `Content-Type: text/html; charset=UTF-8`, `Content-Transfer-Encoding: base64`,
358
+ `MIME-Version: 1.0`, `Content-Type: text/html; charset=UTF-8`, `Content-Transfer-Encoding: quoted-printable`,
323
359
  ].filter(h => h !== null).join("\r\n");
324
- const rawMessage = `${headers}\r\n\r\n${bodyBase64}`;
360
+ const rawMessage = `${headers}\r\n\r\n${bodyEncoded}`;
325
361
  this.imapManager.queueOutgoingLocal(account.id, rawMessage);
326
362
  console.log(` Queued locally: ${msg.subject} via ${account.id} from ${fromHeader}`);
327
363
  for (const addr of msg.to)
@@ -491,15 +527,15 @@ export class MailxService {
491
527
  // Generate or reuse a stable draft ID for dedup
492
528
  const id = draftId || `mailx-draft-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
493
529
  const body = bodyHtml || bodyText || "";
494
- const bodyBase64 = Buffer.from(body, "utf-8").toString("base64").replace(/(.{76})/g, "$1\r\n");
530
+ const bodyEncoded = encodeQuotedPrintable(body);
495
531
  const headers = [
496
532
  `From: ${account.name} <${account.email}>`,
497
533
  to ? `To: ${to}` : null, cc ? `Cc: ${cc}` : null,
498
534
  `Subject: ${subject || "(no subject)"}`, `Date: ${new Date().toUTCString()}`,
499
535
  `X-Mailx-Draft-ID: ${id}`,
500
- `MIME-Version: 1.0`, `Content-Type: text/html; charset=UTF-8`, `Content-Transfer-Encoding: base64`,
536
+ `MIME-Version: 1.0`, `Content-Type: text/html; charset=UTF-8`, `Content-Transfer-Encoding: quoted-printable`,
501
537
  ].filter(h => h !== null).join("\r\n");
502
- const raw = `${headers}\r\n\r\n${bodyBase64}`;
538
+ const raw = `${headers}\r\n\r\n${bodyEncoded}`;
503
539
  const uid = await this.imapManager.saveDraft(accountId, raw, previousDraftUid, id);
504
540
  return { uid, draftId: id };
505
541
  }
@@ -1,58 +0,0 @@
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==