5.1 KiB
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 1–2 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 1–2 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_atwatermark; each digest covers recaps created since that instant, so nothing repeats and nothing is missed.
Data (grounded in code)
- Saved recap record (
server/history.jssaveToHistory):id,title,type,url,createdAt(ISO),topicCount,chunks(topics, each with bullet summaries),entries(transcript),speakers/speakerNames. No top-level summary is stored → the 1–2 paragraph overview must be synthesized. - Multi-mode users live in the
userstable (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 onisSmtpReady()+ public URL set. For each opted-in user, list sessions withcreatedAt > 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.
- Synthesis —
synthesizeEpisodeOverview(record): send the recap's topic titles + bullet summaries to the relay LLM with a "write a 1–2 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). - Email —
renderDigestEmail({ brandName, episodes, manageUrl, unsubscribeUrl })inserver/email-template.js, matching the existing reminder/magic-link templates. - Opt-in storage — migration in
server/db.js: addusers.digest_enabled(default 0) andusers.last_digest_at(ms, nullable). Toggle endpoint inserver/account-routes.js(requires session). Settings-modal toggle inpublic/index.html. - Unsubscribe — a one-click tokenized GET link in every email that flips
digest_enabled = 0without requiring login (signed token), plus the in-app toggle. Consent + deliverability hygiene on the young recaps.cc domain. - Operator test trigger —
POST /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)
- Synthesis cost owner — operator-absorbed (default) vs user credits?
- Send hour — 08:00 server time (default)?
- Single-mode operator digest — defer to a follow-on (default: multi-mode only v1)?
- 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.mdper the cross-repo rule. Resolve before phase 2.
Build phases
- Schema + opt-in toggle (migration, account endpoint, settings UI).
- Synthesis + cache (relay call + write-back + operator-string scrub). Resolve the relay-contract question first.
- Email template + scan loop + scheduler + watermark dedup + overflow cap.
- Operator test trigger.
- Tests — pure-function coverage (episode selection vs watermark, cap/overflow, empty
→ skip), in the
subscription-reminderstest style.