@bobfrankston/rmfmail 1.2.310 → 1.2.313
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 +21 -12
- package/.llm/trusted-lists.md +49 -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 +84 -2
- package/client/app.bundle.js.map +2 -2
- package/client/components/message-viewer.js +128 -1
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +123 -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/client/styles/components.css +3 -0
- package/docs/allowlist.md +28 -3
- package/docs/spam-false-positives.md +23 -0
- package/npmchanges.md +58 -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 +28 -3
- 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
|
@@ -1506,6 +1506,60 @@ export async function showMessage(accountId, uid, folderId, specialUse, isRetry
|
|
|
1506
1506
|
items.push({ label: `✓ Trust all senders at ${dom}`, action: () => void approve("domain", dom) });
|
|
1507
1507
|
items.push({ label: "", action: () => { }, separator: true });
|
|
1508
1508
|
}
|
|
1509
|
+
// "Trust the ‹list› mailing list" (Bob 2026-09-09: "how do I
|
|
1510
|
+
// say that this is a valid mailing list message"). A list
|
|
1511
|
+
// that keeps the poster's From — MIT's Mailman, for a Gmail
|
|
1512
|
+
// author — leaves nothing per-sender to trust: the author
|
|
1513
|
+
// is unproved on every post, by the list's own doing. The
|
|
1514
|
+
// LIST is proved (its host passed SPF for the bounce
|
|
1515
|
+
// address), so the list is what the reader vouches for.
|
|
1516
|
+
// Offered from the chip's own advice, which is the only
|
|
1517
|
+
// thing an unflagged list post shows; the banner has its
|
|
1518
|
+
// own button.
|
|
1519
|
+
//
|
|
1520
|
+
// Offered on every proved post, trusted or not: the write
|
|
1521
|
+
// is a toggle, and once the list is trusted the chip and
|
|
1522
|
+
// banner are gone, so this menu is the only place an
|
|
1523
|
+
// approval can be undone without editing allowlist.jsonc
|
|
1524
|
+
// by hand (Bob 2026-09-09: "how would I undo an approval").
|
|
1525
|
+
const listSpam = msg.spamScore;
|
|
1526
|
+
if (listSpam?.listProved && listSpam.listId) {
|
|
1527
|
+
const listId = listSpam.listId;
|
|
1528
|
+
const untrust = !!listSpam.listTrusted;
|
|
1529
|
+
items.push({
|
|
1530
|
+
label: untrust ? `✗ Stop trusting the ${listId} mailing list` : `✓ Trust the ${listId} mailing list`,
|
|
1531
|
+
action: async () => {
|
|
1532
|
+
const st = document.getElementById("status-sync");
|
|
1533
|
+
try {
|
|
1534
|
+
// A toggle: the answer says which way it went,
|
|
1535
|
+
// and it must match what was asked for — a
|
|
1536
|
+
// mismatch means the write did not land.
|
|
1537
|
+
const res = await trustSenderOrDomain("list", listId);
|
|
1538
|
+
if (!res || !!res.trusted === untrust)
|
|
1539
|
+
throw new Error("the list did not save");
|
|
1540
|
+
listSpam.listTrusted = res.trusted;
|
|
1541
|
+
if (res.trusted) {
|
|
1542
|
+
document.querySelector(".mv-spamscore")?.remove();
|
|
1543
|
+
document.querySelector(".mv-spamscore-action")?.remove();
|
|
1544
|
+
document.querySelector(".mv-trust")?.remove();
|
|
1545
|
+
}
|
|
1546
|
+
if (st)
|
|
1547
|
+
st.textContent = res.trusted
|
|
1548
|
+
? `Trusting the ${listId} mailing list`
|
|
1549
|
+
: `No longer trusting the ${listId} mailing list — reopen the message to see its score again`;
|
|
1550
|
+
}
|
|
1551
|
+
catch (e) {
|
|
1552
|
+
// The reader chose an action and must know whether it took.
|
|
1553
|
+
if (st) {
|
|
1554
|
+
st.textContent = `Couldn't trust the ${listId} list — ${e?.message || e}`;
|
|
1555
|
+
st.style.color = "oklch(0.65 0.2 25)";
|
|
1556
|
+
}
|
|
1557
|
+
console.error("[allowlist] trust list failed:", e);
|
|
1558
|
+
}
|
|
1559
|
+
},
|
|
1560
|
+
});
|
|
1561
|
+
items.push({ label: "", action: () => { }, separator: true });
|
|
1562
|
+
}
|
|
1509
1563
|
}
|
|
1510
1564
|
if (omitted > 0) {
|
|
1511
1565
|
items.push({
|
|
@@ -1860,9 +1914,48 @@ export async function showMessage(accountId, uid, folderId, specialUse, isRetry
|
|
|
1860
1914
|
: "No forgery rule scored. The rules that did are listed below.")
|
|
1861
1915
|
: level === "high"
|
|
1862
1916
|
? "Your mail server scored this at or above its spam threshold and flagged it."
|
|
1863
|
-
: "Below your server's spam threshold, but more than halfway to it.") + why
|
|
1917
|
+
: "Below your server's spam threshold, but more than halfway to it.") + why
|
|
1918
|
+
// The list that provably delivered this, and where the action
|
|
1919
|
+
// lives — the chip is the only thing an unflagged list post
|
|
1920
|
+
// shows, so it has to say how to make it stop (2026-09-09).
|
|
1921
|
+
+ (spam.listProved && spam.listId && !spam.listTrusted
|
|
1922
|
+
? `\n\nDelivered by the ${spam.listId} mailing list, which your server proved sent it. Right-click the From address to trust the list.`
|
|
1923
|
+
: "");
|
|
1864
1924
|
chip.textContent = `spam score ${spam.score} of ${spam.threshold}`;
|
|
1865
1925
|
bodyEl.appendChild(chip);
|
|
1926
|
+
// The action beside the warning it answers (Bob 2026-09-09: "it
|
|
1927
|
+
// would be nice to have an [approve this list] on the red warning
|
|
1928
|
+
// banner"). An unflagged list post has no banner — the chip IS
|
|
1929
|
+
// the warning — so the button sits next to it. Same write as the
|
|
1930
|
+
// From-menu item and the banner button: trustedLists by List-Id.
|
|
1931
|
+
if (spam.listProved && spam.listId && !spam.listTrusted) {
|
|
1932
|
+
const listId = spam.listId;
|
|
1933
|
+
const b = document.createElement("button");
|
|
1934
|
+
b.className = "mv-trust-action mv-spamscore-action";
|
|
1935
|
+
b.textContent = `Trust the ${listId} list`;
|
|
1936
|
+
b.title = `Delivered by the ${listId} mailing list, which your server proved sent it. Trusting the list hides this score on every post the list delivers.`;
|
|
1937
|
+
b.addEventListener("click", async () => {
|
|
1938
|
+
b.disabled = true;
|
|
1939
|
+
try {
|
|
1940
|
+
// Only ever ADDS, so anything but `trusted: true` means
|
|
1941
|
+
// the write did not land — never hide the chip on a
|
|
1942
|
+
// failed save.
|
|
1943
|
+
const res = await trustSenderOrDomain("list", listId);
|
|
1944
|
+
if (!res?.trusted)
|
|
1945
|
+
throw new Error("the list did not save");
|
|
1946
|
+
spam.listTrusted = true;
|
|
1947
|
+
chip.remove();
|
|
1948
|
+
b.remove();
|
|
1949
|
+
document.querySelector(".mv-trust")?.remove();
|
|
1950
|
+
}
|
|
1951
|
+
catch (e) {
|
|
1952
|
+
// The reader pressed a button and must know whether it took.
|
|
1953
|
+
b.disabled = false;
|
|
1954
|
+
b.textContent = `Could not save: ${e?.message || e}`;
|
|
1955
|
+
}
|
|
1956
|
+
});
|
|
1957
|
+
bodyEl.appendChild(b);
|
|
1958
|
+
}
|
|
1866
1959
|
}
|
|
1867
1960
|
const trust = (msg.trust || []);
|
|
1868
1961
|
if (trust.length) {
|
|
@@ -1941,6 +2034,40 @@ export async function showMessage(accountId, uid, folderId, specialUse, isRetry
|
|
|
1941
2034
|
}
|
|
1942
2035
|
warn.appendChild(actions);
|
|
1943
2036
|
}
|
|
2037
|
+
// "Trust the ‹list› list" (Bob 2026-09-09: "how do I say that this
|
|
2038
|
+
// is a valid mailing list message"). A finding carries `list` only
|
|
2039
|
+
// when the receiving server proved the list's host sent the
|
|
2040
|
+
// message and the list is not yet trusted; the entry it writes
|
|
2041
|
+
// (`trustedLists`, keyed on List-Id) counts only on such mail.
|
|
2042
|
+
// Not on a danger banner, for the same reason as every other
|
|
2043
|
+
// button here.
|
|
2044
|
+
const lists = worst === "danger" ? [] : Array.from(new Set(trust.map(f => f.list).filter(Boolean)));
|
|
2045
|
+
if (lists.length) {
|
|
2046
|
+
const actions = document.createElement("div");
|
|
2047
|
+
actions.className = "mv-trust-actions";
|
|
2048
|
+
for (const listId of lists) {
|
|
2049
|
+
const b = document.createElement("button");
|
|
2050
|
+
b.className = "mv-trust-action";
|
|
2051
|
+
b.textContent = `Trust the ${listId} list`;
|
|
2052
|
+
b.addEventListener("click", async () => {
|
|
2053
|
+
b.disabled = true;
|
|
2054
|
+
try {
|
|
2055
|
+
const res = await trustSenderOrDomain("list", listId);
|
|
2056
|
+
if (!res?.trusted)
|
|
2057
|
+
throw new Error("the list did not save");
|
|
2058
|
+
warn.remove();
|
|
2059
|
+
document.querySelector(".mv-spamscore")?.remove();
|
|
2060
|
+
}
|
|
2061
|
+
catch (e) {
|
|
2062
|
+
// The reader pressed a button and must know whether it took.
|
|
2063
|
+
b.disabled = false;
|
|
2064
|
+
b.textContent = `Could not save: ${e?.message || e}`;
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
actions.appendChild(b);
|
|
2068
|
+
}
|
|
2069
|
+
warn.appendChild(actions);
|
|
2070
|
+
}
|
|
1944
2071
|
// "Trust ‹service› as an intermediary" (Bob 2026-09-08: "how do I
|
|
1945
2072
|
// mark zoom and sites like that as trusted intermediaries?"). A
|
|
1946
2073
|
// finding carries `via` only when a party PROVABLY handled the
|