Files
recap/docs/daily-digest-plan.md
T

5.1 KiB
Raw Blame History

Daily Digest — plan

Status: proposed (awaiting go-ahead). Captures the design agreed with Grant on 2026-06-15. Build only after sign-off.

Goal

An opt-in (off by default) daily "wake-up" email to recaps.cc users: the recaps added to their library in the last ~24 hours, each shown as a synthesized 12 paragraph overview generated from that recap's existing per-topic summaries. Turns passive subscriptions into a daily touchpoint without making the user open the app.

Decisions (locked 2026-06-15)

  • Content — "overnight recaps": library additions since the user's last digest.
  • Audience / opt-in — multi-mode (recaps.cc) first; off by default; per-user toggle.
  • Per-episode depth — a 12 paragraph overview synthesized from the stored topic summaries (chunks). NOT raw full text (too long, Gmail clips >~102 KB), NOT a one-sentence blurb (too thin). This is Grant's call and it's what bounds email size.
  • Volume — per-episode size is bounded by the 2-paragraph synthesis. Still cap at ~10 episodes per email with an "and N more in your library →" overflow link for extreme days.
  • Cadence — once per user per ~24h at a fixed server-time hour (default 08:00). Timezone-aware send is a v2. Skip the email entirely when nothing is new.
  • Dedup — a per-user last_digest_at watermark; each digest covers recaps created since that instant, so nothing repeats and nothing is missed.

Data (grounded in code)

  • Saved recap record (server/history.js saveToHistory): id, title, type, url, createdAt (ISO), topicCount, chunks (topics, each with bullet summaries), entries (transcript), speakers/speakerNames. No top-level summary is stored → the 12 paragraph overview must be synthesized.
  • Multi-mode users live in the users table (id, email, …); a user's library scope is their user id.

Architecture

Mirror server/subscription-reminders.js (the proven daily-scan-plus-email pattern: self-gating, deduped, never throws).

  • server/daily-digest.js (new)
    • runDigestScan({ force }): gate on isSmtpReady() + public URL set. For each opted-in user, list sessions with createdAt > last_digest_at; if none, skip. For each new recap, get-or-generate its overview (see below), render the email, sendMail, then advance the watermark. Returns a {sent, skipped} summary; never throws.
    • startDigestScheduler(): boot delay + interval, fires near the target hour. Idempotent; safe to start unconditionally in multi mode.
  • SynthesissynthesizeEpisodeOverview(record): send the recap's topic titles + bullet summaries to the relay LLM with a "write a 12 paragraph overview" prompt. Cache the result back onto the session JSON (e.g. digestOverview) so it's generated once and could later power an in-app episode overview. Sanitize operator-internal strings at this boundary (Parakeet/CUDA/LAN IPs etc. must not reach cloud users — existing repo convention).
  • EmailrenderDigestEmail({ brandName, episodes, manageUrl, unsubscribeUrl }) in server/email-template.js, matching the existing reminder/magic-link templates.
  • Opt-in storage — migration in server/db.js: add users.digest_enabled (default 0) and users.last_digest_at (ms, nullable). Toggle endpoint in server/account-routes.js (requires session). Settings-modal toggle in public/index.html.
  • Unsubscribe — a one-click tokenized GET link in every email that flips digest_enabled = 0 without requiring login (signed token), plus the in-app toggle. Consent + deliverability hygiene on the young recaps.cc domain.
  • Operator test triggerPOST /api/admin/digest/run { test_email }, mirroring the reminders test hook, so it can be smoke-tested without waiting a day.

Cost / credits

The synthesis is one small relay LLM call per new recap per opted-in user, run once and cached. Bounded by (opted-in users × new recaps/day). Recommend operator-absorbed (it's a retention feature, input is already-short topic summaries) rather than drawing the user's credits. Confirm.

Open questions (defaults chosen; confirm or adjust)

  1. Synthesis cost owner — operator-absorbed (default) vs user credits?
  2. Send hour — 08:00 server time (default)?
  3. Single-mode operator digest — defer to a follow-on (default: multi-mode only v1)?
  4. Relay contract — does an existing relay endpoint (/relay/analyze) fit the "summarize these topic summaries into 2 paragraphs" call, or is a small new relay capability/prompt-mode needed? If new, update ../recap-relay + both repos' AGENTS.md/ROADMAP.md per the cross-repo rule. Resolve before phase 2.

Build phases

  1. Schema + opt-in toggle (migration, account endpoint, settings UI).
  2. Synthesis + cache (relay call + write-back + operator-string scrub). Resolve the relay-contract question first.
  3. Email template + scan loop + scheduler + watermark dedup + overflow cap.
  4. Operator test trigger.
  5. Tests — pure-function coverage (episode selection vs watermark, cap/overflow, empty → skip), in the subscription-reminders test style.