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

94 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
- **Synthesis** — `synthesizeEpisodeOverview(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).
- **Email** — `renderDigestEmail({ 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 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)
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.