@bobfrankston/mailx 1.0.182 → 1.0.183

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.182",
3
+ "version": "1.0.183",
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.2",
25
25
  "@bobfrankston/miscinfo": "^1.0.8",
26
26
  "@bobfrankston/oauthsupport": "^1.0.21",
27
- "@bobfrankston/msger": "^0.1.232",
27
+ "@bobfrankston/msger": "^0.1.233",
28
28
  "@capacitor/android": "^8.3.0",
29
29
  "@capacitor/cli": "^8.3.0",
30
30
  "@capacitor/core": "^8.3.0",
@@ -178,7 +178,7 @@ export declare class ImapManager extends EventEmitter {
178
178
  private readonly hostname;
179
179
  /** Ensure Outbox folder exists, create if needed */
180
180
  private ensureOutbox;
181
- /** Save a debug copy of outgoing mail to the sending directory */
181
+ /** Save a copy of outgoing mail label is a subdirectory (editing/queued/sent) */
182
182
  private saveSendingCopy;
183
183
  /** Queue a message for sending. Tries IMAP Outbox, falls back to local file. */
184
184
  queueOutgoing(accountId: string, rawMessage: string | Buffer): Promise<void>;
@@ -1629,20 +1629,19 @@ export class ImapManager extends EventEmitter {
1629
1629
  outbox = this.findFolder(accountId, "outbox");
1630
1630
  return outbox?.path || "Outbox";
1631
1631
  }
1632
- /** Save a debug copy of outgoing mail to the sending directory */
1632
+ /** Save a copy of outgoing mail label is a subdirectory (editing/queued/sent) */
1633
1633
  saveSendingCopy(accountId, rawMessage, label) {
1634
1634
  try {
1635
- const sendingDir = path.join(getConfigDir(), "sending", accountId);
1636
- fs.mkdirSync(sendingDir, { recursive: true });
1635
+ const dir = path.join(getConfigDir(), "sending", accountId, label);
1636
+ fs.mkdirSync(dir, { recursive: true });
1637
1637
  const now = new Date();
1638
1638
  const pad2 = (n) => String(n).padStart(2, "0");
1639
1639
  const ts = `${now.getFullYear()}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}_${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`;
1640
- const filename = `${ts}-${label}.eml`;
1641
- fs.writeFileSync(path.join(sendingDir, filename), rawMessage);
1642
- console.log(` [sending] Saved debug copy: ${filename}`);
1640
+ fs.writeFileSync(path.join(dir, `${ts}.eml`), rawMessage);
1641
+ console.log(` [sending] ${label}/${ts}.eml`);
1643
1642
  }
1644
1643
  catch (e) {
1645
- console.error(` [sending] Failed to save debug copy: ${e.message}`);
1644
+ console.error(` [sending] Failed to save copy: ${e.message}`);
1646
1645
  }
1647
1646
  }
1648
1647
  /** Queue a message for sending. Tries IMAP Outbox, falls back to local file. */
@@ -4,7 +4,9 @@
4
4
  * Both the Express API (mailx-api) and the Android bridge call these functions.
5
5
  */
6
6
  import * as dns from "node:dns/promises";
7
- import { loadSettings, saveSettings, loadAccounts, loadAccountsAsync, saveAccounts, initCloudConfig, loadAllowlist, saveAllowlist, loadAutocomplete, saveAutocomplete, getStorePath, getStorageInfo } from "@bobfrankston/mailx-settings";
7
+ import * as fs from "node:fs";
8
+ import * as path from "node:path";
9
+ import { loadSettings, saveSettings, loadAccounts, loadAccountsAsync, saveAccounts, initCloudConfig, loadAllowlist, saveAllowlist, loadAutocomplete, saveAutocomplete, getStorePath, getStorageInfo, getConfigDir } from "@bobfrankston/mailx-settings";
8
10
  import { simpleParser } from "mailparser";
9
11
  // ── Quoted-printable encoding (readable in debug .eml files) ──
10
12
  function encodeQuotedPrintable(text) {
@@ -538,6 +540,21 @@ export class MailxService {
538
540
  `MIME-Version: 1.0`, `Content-Type: text/html; charset=UTF-8`, `Content-Transfer-Encoding: quoted-printable`,
539
541
  ].filter(h => h !== null).join("\r\n");
540
542
  const raw = `${headers}\r\n\r\n${bodyEncoded}`;
543
+ // Save local editing copy — crash recovery, keep last 3
544
+ try {
545
+ const editingDir = path.join(getConfigDir(), "sending", accountId, "editing");
546
+ fs.mkdirSync(editingDir, { recursive: true });
547
+ const pad2 = (n) => String(n).padStart(2, "0");
548
+ const now = new Date();
549
+ const ts = `${now.getFullYear()}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}_${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`;
550
+ fs.writeFileSync(path.join(editingDir, `${ts}.eml`), raw);
551
+ // Keep only last 3
552
+ const files = fs.readdirSync(editingDir).filter(f => f.endsWith(".eml")).sort();
553
+ while (files.length > 3) {
554
+ fs.unlinkSync(path.join(editingDir, files.shift()));
555
+ }
556
+ }
557
+ catch { /* ignore */ }
541
558
  const uid = await this.imapManager.saveDraft(accountId, raw, previousDraftUid, id);
542
559
  return { uid, draftId: id };
543
560
  }