Digest polish: honest on-site wording, one-tap opt-in after sign-in, List-Unsubscribe

* On-site end-cap now says "You're caught up for now." — honest, since Highlights
  refreshes through the day (the email keeps the daily "see you tomorrow").
* Anonymous "Get tomorrow's brief by email" now honors the one-tap promise:
  sets a pending flag, opens sign-in, and auto-enables once auth resolves.
* Email compliance (RFC 2369/8058): send_email takes optional headers; the digest
  sets List-Unsubscribe + List-Unsubscribe-Post=One-Click, and a POST
  /api/digest/unsubscribe handles native one-click (GET still serves the page).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-09 16:35:05 -04:00
parent cf5cbb33c0
commit 1956d7fd23
6 changed files with 42 additions and 12 deletions
+14 -5
View File
@@ -528,16 +528,25 @@ def create_app() -> FastAPI:
conn.commit()
return {"ok": True, "digest_enabled": body.enabled}
@app.get("/api/digest/unsubscribe", response_class=HTMLResponse)
def digest_unsubscribe(u: int = Query(...), t: str = Query(...)) -> HTMLResponse:
# One-click, no login: match the per-user token, then turn the digest off.
ok = False
def _do_unsubscribe(u: int, t: str) -> bool:
with get_conn() as conn:
row = conn.execute("SELECT digest_unsub_token FROM users WHERE id = ?", (u,)).fetchone()
if row and row["digest_unsub_token"] and hmac.compare_digest(row["digest_unsub_token"], t):
conn.execute("UPDATE users SET digest_enabled = 0 WHERE id = ?", (u,))
conn.commit()
ok = True
return True
return False
@app.post("/api/digest/unsubscribe")
def digest_unsubscribe_oneclick(u: int = Query(...), t: str = Query(...)) -> dict:
# RFC 8058 one-click: the mailbox provider POSTs here; just do it + 200.
_do_unsubscribe(u, t)
return {"ok": True}
@app.get("/api/digest/unsubscribe", response_class=HTMLResponse)
def digest_unsubscribe(u: int = Query(...), t: str = Query(...)) -> HTMLResponse:
# One-click, no login: match the per-user token, then turn the digest off.
ok = _do_unsubscribe(u, t)
msg = (
"Youre unsubscribed from the daily digest. No hard feelings — "
"Upbeat Bytes is always here when you want it."
+5 -2
View File
@@ -148,9 +148,12 @@ def send_due_digests(conn: sqlite3.Connection, force: bool = False, base: str |
conn.execute("UPDATE users SET digest_unsub_token = ? WHERE id = ?", (token, user["id"]))
conn.commit()
user["digest_unsub_token"] = token
subject, text, html = build_digest(items, brief_date, unsub_url(user, base), base)
link = unsub_url(user, base)
subject, text, html = build_digest(items, brief_date, link, base)
# RFC 2369 / 8058: let inboxes offer native one-click unsubscribe.
headers = {"List-Unsubscribe": f"<{link}>", "List-Unsubscribe-Post": "List-Unsubscribe=One-Click"}
try:
email_send.send_email(user["email"], subject, text, html=html)
email_send.send_email(user["email"], subject, text, html=html, headers=headers)
except Exception: # noqa: BLE001 — one bad send shouldn't stop the rest; retry next window
continue
conn.execute(
+4 -1
View File
@@ -30,7 +30,8 @@ def _cfg() -> dict:
}
def send_email(to: str, subject: str, text: str, html: str | None = None, reply_to: str | None = None) -> None:
def send_email(to: str, subject: str, text: str, html: str | None = None, reply_to: str | None = None,
headers: dict | None = None) -> None:
"""Send one message. Raises on failure (caller decides how loud to be)."""
cfg = _cfg()
if not cfg["host"]:
@@ -40,6 +41,8 @@ def send_email(to: str, subject: str, text: str, html: str | None = None, reply_
msg["To"] = to
if reply_to:
msg["Reply-To"] = reply_to
for key, value in (headers or {}).items():
msg[key] = value
msg["Subject"] = subject
msg.set_content(text)
if html: