@bobfrankston/rmfmail 1.2.310 → 1.2.312
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/.commitmsg +39 -13
- package/.llm/trusted-lists.md +46 -0
- package/TODO.md +2 -2
- package/client/android-bootstrap.bundle.js +78 -15
- package/client/android-bootstrap.bundle.js.map +2 -2
- package/client/app.bundle.js +57 -2
- package/client/app.bundle.js.map +2 -2
- package/client/components/message-viewer.js +82 -1
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +79 -2
- package/client/compose/compose.bundle.js +1 -1
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/lib/api-client.js +1 -1
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +3 -3
- package/docs/allowlist.md +25 -2
- package/docs/spam-false-positives.md +23 -0
- package/npmchanges.md +16 -0
- package/package.json +5 -5
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/index.d.ts +3 -1
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +4 -2
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +6 -4
- package/packages/mailx-service/package.json +1 -1
- package/packages/mailx-settings/docs/allowlist.md +25 -2
- package/packages/mailx-settings/docs/spam-false-positives.md +23 -0
- package/packages/mailx-settings/index.d.ts +1 -0
- package/packages/mailx-settings/index.d.ts.map +1 -1
- package/packages/mailx-settings/index.js +7 -0
- package/packages/mailx-settings/index.js.map +1 -1
- package/packages/mailx-settings/index.ts +7 -0
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store/store.d.ts.map +1 -1
- package/packages/mailx-store/store.js +3 -0
- package/packages/mailx-store/store.js.map +1 -1
- package/packages/mailx-store/store.ts +3 -0
- package/packages/mailx-store-web/package.json +1 -1
- package/packages/mailx-store-web/web-service.d.ts +3 -2
- package/packages/mailx-store-web/web-service.d.ts.map +1 -1
- package/packages/mailx-store-web/web-service.js +4 -2
- package/packages/mailx-store-web/web-service.js.map +1 -1
- package/packages/mailx-store-web/web-service.ts +6 -4
- package/packages/mailx-store-web/web-settings.d.ts +1 -0
- package/packages/mailx-store-web/web-settings.d.ts.map +1 -1
- package/packages/mailx-store-web/web-settings.js +2 -0
- package/packages/mailx-store-web/web-settings.js.map +1 -1
- package/packages/mailx-store-web/web-settings.ts +2 -0
- package/packages/mailx-types/index.d.ts +2 -2
- package/packages/mailx-types/index.d.ts.map +1 -1
- package/packages/mailx-types/index.js +1 -1
- package/packages/mailx-types/index.js.map +1 -1
- package/packages/mailx-types/index.ts +2 -2
- package/packages/mailx-types/package.json +1 -1
- package/packages/mailx-types/trust.d.ts +35 -0
- package/packages/mailx-types/trust.d.ts.map +1 -1
- package/packages/mailx-types/trust.js +115 -8
- package/packages/mailx-types/trust.js.map +1 -1
- package/packages/mailx-types/trust.ts +139 -8
|
@@ -1371,6 +1371,44 @@ export async function showMessage(accountId: string, uid: number, folderId?: num
|
|
|
1371
1371
|
if (dom) items.push({ label: `✓ Trust all senders at ${dom}`, action: () => void approve("domain", dom) });
|
|
1372
1372
|
items.push({ label: "", action: () => {}, separator: true });
|
|
1373
1373
|
}
|
|
1374
|
+
// "Trust the ‹list› mailing list" (Bob 2026-09-09: "how do I
|
|
1375
|
+
// say that this is a valid mailing list message"). A list
|
|
1376
|
+
// that keeps the poster's From — MIT's Mailman, for a Gmail
|
|
1377
|
+
// author — leaves nothing per-sender to trust: the author
|
|
1378
|
+
// is unproved on every post, by the list's own doing. The
|
|
1379
|
+
// LIST is proved (its host passed SPF for the bounce
|
|
1380
|
+
// address), so the list is what the reader vouches for.
|
|
1381
|
+
// Offered from the chip's own advice, which is the only
|
|
1382
|
+
// thing an unflagged list post shows; the banner has its
|
|
1383
|
+
// own button.
|
|
1384
|
+
const listSpam = (msg as any).spamScore as { listId?: string; listProved?: boolean; listTrusted?: boolean };
|
|
1385
|
+
if (listSpam?.listProved && listSpam.listId && !listSpam.listTrusted) {
|
|
1386
|
+
const listId = listSpam.listId;
|
|
1387
|
+
items.push({
|
|
1388
|
+
label: `✓ Trust the ${listId} mailing list`,
|
|
1389
|
+
action: async () => {
|
|
1390
|
+
const st = document.getElementById("status-sync");
|
|
1391
|
+
try {
|
|
1392
|
+
// The action only ever ADDS, so anything but
|
|
1393
|
+
// `trusted: true` means the write did not land.
|
|
1394
|
+
const res = await trustSenderOrDomain("list", listId);
|
|
1395
|
+
if (!res?.trusted) throw new Error("the list did not save");
|
|
1396
|
+
listSpam.listTrusted = true;
|
|
1397
|
+
document.querySelector(".mv-spamscore")?.remove();
|
|
1398
|
+
document.querySelector(".mv-trust")?.remove();
|
|
1399
|
+
if (st) st.textContent = `Trusting the ${listId} mailing list`;
|
|
1400
|
+
} catch (e: any) {
|
|
1401
|
+
// The reader chose an action and must know whether it took.
|
|
1402
|
+
if (st) {
|
|
1403
|
+
st.textContent = `Couldn't trust the ${listId} list — ${e?.message || e}`;
|
|
1404
|
+
st.style.color = "oklch(0.65 0.2 25)";
|
|
1405
|
+
}
|
|
1406
|
+
console.error("[allowlist] trust list failed:", e);
|
|
1407
|
+
}
|
|
1408
|
+
},
|
|
1409
|
+
});
|
|
1410
|
+
items.push({ label: "", action: () => {}, separator: true });
|
|
1411
|
+
}
|
|
1374
1412
|
}
|
|
1375
1413
|
if (omitted > 0) {
|
|
1376
1414
|
items.push({
|
|
@@ -1678,6 +1716,7 @@ export async function showMessage(accountId: string, uid: number, folderId?: num
|
|
|
1678
1716
|
score: number; threshold: number; flagged: boolean;
|
|
1679
1717
|
kind?: string; proved?: boolean; provedBy?: string; bulkOnly?: boolean;
|
|
1680
1718
|
reasons?: string[]; trusted?: boolean;
|
|
1719
|
+
listId?: string; listProved?: boolean; listTrusted?: boolean;
|
|
1681
1720
|
};
|
|
1682
1721
|
// A sender the reader has approved, whose identity is proved: the chip
|
|
1683
1722
|
// goes with the banner. Repeating the score after the reader has said
|
|
@@ -1715,12 +1754,18 @@ export async function showMessage(accountId: string, uid: number, folderId?: num
|
|
|
1715
1754
|
: "No forgery rule scored. The rules that did are listed below.")
|
|
1716
1755
|
: level === "high"
|
|
1717
1756
|
? "Your mail server scored this at or above its spam threshold and flagged it."
|
|
1718
|
-
: "Below your server's spam threshold, but more than halfway to it.") + why
|
|
1757
|
+
: "Below your server's spam threshold, but more than halfway to it.") + why
|
|
1758
|
+
// The list that provably delivered this, and where the action
|
|
1759
|
+
// lives — the chip is the only thing an unflagged list post
|
|
1760
|
+
// shows, so it has to say how to make it stop (2026-09-09).
|
|
1761
|
+
+ (spam.listProved && spam.listId && !spam.listTrusted
|
|
1762
|
+
? `\n\nDelivered by the ${spam.listId} mailing list, which your server proved sent it. Right-click the From address to trust the list.`
|
|
1763
|
+
: "");
|
|
1719
1764
|
chip.textContent = `spam score ${spam.score} of ${spam.threshold}`;
|
|
1720
1765
|
bodyEl.appendChild(chip);
|
|
1721
1766
|
}
|
|
1722
1767
|
|
|
1723
|
-
const trust = ((msg as any).trust || []) as { id: string; severity: string; text: string; detail: string; via?: string }[];
|
|
1768
|
+
const trust = ((msg as any).trust || []) as { id: string; severity: string; text: string; detail: string; via?: string; list?: string }[];
|
|
1724
1769
|
if (trust.length) {
|
|
1725
1770
|
// An `info` finding is not an accusation, so it gets neither the
|
|
1726
1771
|
// headline nor the "do not open anything" footer — it renders as a
|
|
@@ -1796,6 +1841,38 @@ export async function showMessage(accountId: string, uid: number, folderId?: num
|
|
|
1796
1841
|
}
|
|
1797
1842
|
warn.appendChild(actions);
|
|
1798
1843
|
}
|
|
1844
|
+
// "Trust the ‹list› list" (Bob 2026-09-09: "how do I say that this
|
|
1845
|
+
// is a valid mailing list message"). A finding carries `list` only
|
|
1846
|
+
// when the receiving server proved the list's host sent the
|
|
1847
|
+
// message and the list is not yet trusted; the entry it writes
|
|
1848
|
+
// (`trustedLists`, keyed on List-Id) counts only on such mail.
|
|
1849
|
+
// Not on a danger banner, for the same reason as every other
|
|
1850
|
+
// button here.
|
|
1851
|
+
const lists = worst === "danger" ? [] : Array.from(new Set(trust.map(f => f.list).filter(Boolean)));
|
|
1852
|
+
if (lists.length) {
|
|
1853
|
+
const actions = document.createElement("div");
|
|
1854
|
+
actions.className = "mv-trust-actions";
|
|
1855
|
+
for (const listId of lists) {
|
|
1856
|
+
const b = document.createElement("button");
|
|
1857
|
+
b.className = "mv-trust-action";
|
|
1858
|
+
b.textContent = `Trust the ${listId} list`;
|
|
1859
|
+
b.addEventListener("click", async () => {
|
|
1860
|
+
b.disabled = true;
|
|
1861
|
+
try {
|
|
1862
|
+
const res = await trustSenderOrDomain("list", listId);
|
|
1863
|
+
if (!res?.trusted) throw new Error("the list did not save");
|
|
1864
|
+
warn.remove();
|
|
1865
|
+
document.querySelector(".mv-spamscore")?.remove();
|
|
1866
|
+
} catch (e: any) {
|
|
1867
|
+
// The reader pressed a button and must know whether it took.
|
|
1868
|
+
b.disabled = false;
|
|
1869
|
+
b.textContent = `Could not save: ${e?.message || e}`;
|
|
1870
|
+
}
|
|
1871
|
+
});
|
|
1872
|
+
actions.appendChild(b);
|
|
1873
|
+
}
|
|
1874
|
+
warn.appendChild(actions);
|
|
1875
|
+
}
|
|
1799
1876
|
// "Trust ‹service› as an intermediary" (Bob 2026-09-08: "how do I
|
|
1800
1877
|
// mark zoom and sites like that as trusted intermediaries?"). A
|
|
1801
1878
|
// finding carries `via` only when a party PROVABLY handled the
|
|
@@ -1353,7 +1353,7 @@ function trustSenderOrDomain(type, value) {
|
|
|
1353
1353
|
return ipc().trustSenderOrDomain?.(type, value) ?? Promise.resolve({ trusted: false });
|
|
1354
1354
|
}
|
|
1355
1355
|
function getAllowlist() {
|
|
1356
|
-
return ipc().getAllowlist?.() ?? Promise.resolve({ senders: [], domains: [], recipients: [], flaggedSenders: [], flaggedDomains: [], trustedSenders: [], trustedDomains: [], trustedIntermediaries: [] });
|
|
1356
|
+
return ipc().getAllowlist?.() ?? Promise.resolve({ senders: [], domains: [], recipients: [], flaggedSenders: [], flaggedDomains: [], trustedSenders: [], trustedDomains: [], trustedIntermediaries: [], trustedLists: [] });
|
|
1357
1357
|
}
|
|
1358
1358
|
function flagSenderOrDomain(type, value) {
|
|
1359
1359
|
return ipc().flagSenderOrDomain?.(type, value) ?? Promise.resolve({ flagged: false });
|