@bobfrankston/mailx 1.0.233 → 1.0.234
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":1344,"width":2151,"x":
|
|
1
|
+
{"height":1344,"width":2151,"x":656,"y":239}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.234",
|
|
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.22",
|
|
27
|
-
"@bobfrankston/msger": "^0.1.
|
|
27
|
+
"@bobfrankston/msger": "^0.1.296",
|
|
28
28
|
"@capacitor/android": "^8.3.0",
|
|
29
29
|
"@capacitor/cli": "^8.3.0",
|
|
30
30
|
"@capacitor/core": "^8.3.0",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"@bobfrankston/iflow-node": "^0.1.2",
|
|
79
79
|
"@bobfrankston/miscinfo": "^1.0.8",
|
|
80
80
|
"@bobfrankston/oauthsupport": "^1.0.22",
|
|
81
|
-
"@bobfrankston/msger": "^0.1.
|
|
81
|
+
"@bobfrankston/msger": "^0.1.296",
|
|
82
82
|
"@capacitor/android": "^8.3.0",
|
|
83
83
|
"@capacitor/cli": "^8.3.0",
|
|
84
84
|
"@capacitor/core": "^8.3.0",
|
|
@@ -1957,6 +1957,8 @@ export class ImapManager extends EventEmitter {
|
|
|
1957
1957
|
const ccMatch = raw.match(/^Cc:\s*(.+)$/mi);
|
|
1958
1958
|
const bccMatch = raw.match(/^Bcc:\s*(.+)$/mi);
|
|
1959
1959
|
const fromMatch = raw.match(/^From:\s*(.+)$/mi);
|
|
1960
|
+
const subjectMatch = raw.match(/^Subject:\s*(.+)$/mi);
|
|
1961
|
+
const messageIdMatch = raw.match(/^Message-ID:\s*(<[^>]+>)/mi);
|
|
1960
1962
|
const recipients = [
|
|
1961
1963
|
...(toMatch ? parseAddrs(toMatch[1]) : []),
|
|
1962
1964
|
...(ccMatch ? parseAddrs(ccMatch[1]) : []),
|
|
@@ -1965,9 +1967,22 @@ export class ImapManager extends EventEmitter {
|
|
|
1965
1967
|
const sender = fromMatch ? (parseAddrs(fromMatch[1])[0] || account.email) : account.email;
|
|
1966
1968
|
if (recipients.length === 0)
|
|
1967
1969
|
throw new Error("No recipients");
|
|
1970
|
+
// Dedup: skip if this Message-ID has already been sent. Prevents the
|
|
1971
|
+
// outbox from re-sending the same file across crash/restart cycles.
|
|
1972
|
+
// Without this, a queued .ltr that was mid-delivery when mailx crashed
|
|
1973
|
+
// would be re-sent on every startup until the rename loop completed.
|
|
1974
|
+
const messageId = messageIdMatch ? messageIdMatch[1] : "";
|
|
1975
|
+
if (messageId && this.db.hasSentMessage(messageId)) {
|
|
1976
|
+
console.log(` [smtp] ${accountId}: SKIP ${messageId} — already in sent_log`);
|
|
1977
|
+
return; // caller will move the file to sent/ without re-sending
|
|
1978
|
+
}
|
|
1968
1979
|
const rawToSend = raw.replace(/^Bcc:.*\r?\n/mi, "");
|
|
1969
1980
|
this.saveSendingCopy(accountId, rawToSend, "sent");
|
|
1970
1981
|
await transport.sendMail({ envelope: { from: sender, to: recipients }, raw: rawToSend });
|
|
1982
|
+
// Record the successful send so future attempts dedupe against it.
|
|
1983
|
+
if (messageId) {
|
|
1984
|
+
this.db.recordSent(messageId, accountId, subjectMatch?.[1]?.trim() || "", recipients);
|
|
1985
|
+
}
|
|
1971
1986
|
console.log(` [smtp] ${accountId}: sent to ${recipients.join(", ")}`);
|
|
1972
1987
|
}
|
|
1973
1988
|
/** Process Outbox — send pending messages with flag-based interlock */
|
|
@@ -7,6 +7,11 @@ import type { MessageEnvelope, Folder, EmailAddress, PagedResult, MessageQuery }
|
|
|
7
7
|
export declare class MailxDB {
|
|
8
8
|
private db;
|
|
9
9
|
constructor(dbDir: string);
|
|
10
|
+
/** Has this Message-ID already been sent? Used to prevent the outbox from
|
|
11
|
+
* re-sending the same raw file across crash/restart cycles. */
|
|
12
|
+
hasSentMessage(messageId: string): boolean;
|
|
13
|
+
/** Record a successfully sent message so future attempts are skipped. */
|
|
14
|
+
recordSent(messageId: string, accountId: string, subject: string, recipients: string[]): void;
|
|
10
15
|
/** Idempotently add a column to a table if it's missing. */
|
|
11
16
|
private addColumnIfMissing;
|
|
12
17
|
/** Compute a thread id for an incoming message. Strategy:
|
|
@@ -62,6 +62,14 @@ const SCHEMA = `
|
|
|
62
62
|
CREATE INDEX IF NOT EXISTS idx_messages_thread_id
|
|
63
63
|
ON messages(account_id, thread_id);
|
|
64
64
|
|
|
65
|
+
CREATE TABLE IF NOT EXISTS sent_log (
|
|
66
|
+
message_id TEXT PRIMARY KEY,
|
|
67
|
+
account_id TEXT NOT NULL,
|
|
68
|
+
subject TEXT DEFAULT '',
|
|
69
|
+
recipients TEXT DEFAULT '',
|
|
70
|
+
sent_at INTEGER NOT NULL
|
|
71
|
+
);
|
|
72
|
+
|
|
65
73
|
CREATE TABLE IF NOT EXISTS queue (
|
|
66
74
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
67
75
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
@@ -137,6 +145,26 @@ export class MailxDB {
|
|
|
137
145
|
}
|
|
138
146
|
catch { /* already exists */ }
|
|
139
147
|
}
|
|
148
|
+
// ── Sent-log (dedup) ──
|
|
149
|
+
/** Has this Message-ID already been sent? Used to prevent the outbox from
|
|
150
|
+
* re-sending the same raw file across crash/restart cycles. */
|
|
151
|
+
hasSentMessage(messageId) {
|
|
152
|
+
if (!messageId)
|
|
153
|
+
return false;
|
|
154
|
+
const row = this.db.prepare("SELECT 1 FROM sent_log WHERE message_id = ? LIMIT 1").get(messageId);
|
|
155
|
+
return !!row;
|
|
156
|
+
}
|
|
157
|
+
/** Record a successfully sent message so future attempts are skipped. */
|
|
158
|
+
recordSent(messageId, accountId, subject, recipients) {
|
|
159
|
+
if (!messageId)
|
|
160
|
+
return;
|
|
161
|
+
try {
|
|
162
|
+
this.db.prepare("INSERT INTO sent_log (message_id, account_id, subject, recipients, sent_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(message_id) DO NOTHING").run(messageId, accountId, subject || "", recipients.join(", "), Date.now());
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
console.error(` [sent_log] failed to record ${messageId}: ${e.message}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
140
168
|
/** Idempotently add a column to a table if it's missing. */
|
|
141
169
|
addColumnIfMissing(table, column, sqlType) {
|
|
142
170
|
try {
|