Files
silo-server/docs/superpowers/plans/notifications/06-v1.5-roadmap.md
QuickandClaude Fable 5 a24279e3e3 docs(notifications): import notification design docs, add web push + v1.5 specs
Imports the notification system design folder (architecture overview,
release-events/inbox foundation, APNs/FCM relay specs, outbound webhooks)
and adds the Web Push spec (05, implemented in this branch), the shared
outbound-email architecture note, and the v1.5 roadmap (06) covering the
remaining work after APNs/FCM were deferred to v2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 14:55:46 -04:00

8.3 KiB

Notifications v1.5 Roadmap

Date: 2026-06-11 Status: Draft (work not started) Scope: The remaining notification work between the shipped v1 and the deferred v2 push channels. Each item is independent and sized to land as its own PR. Depends On:

Where v1 landed (context for this doc)

Implemented 2026-06-11: the full foundation (availability seeding, release events, interest index, fanout worker with burst caps, websocket channel with ticket handshake, inbox/sync/preferences/capability APIs, web inbox + badge + settings), outbound webhooks (Discord + generic HMAC, SSRF guard, durable outbox, retry + auto-disable), Web Push (VAPID self-provisioned, E2E-encrypted payloads, service worker), and the shared SMTP core (internal/mail, see docs/architecture/email.md) with an admin Email settings page — but no feature consuming email yet.

Deferred to v2 by explicit decision: APNs (02) and FCM (03) — they require Silo-operated relay infrastructure and developer accounts. Also v2 per the original plans: movie availability, aggregated notifications ("3 new episodes"), quiet hours, cross-profile views.


1. Admin settings UI for notification controls

Why: every notifications.* setting works today but is reachable only through the raw admin settings API. Admins should not need curl to find the kill switches.

What: an admin settings page ("Notifications", next to the Email page added in v1) exposing:

Group Keys
Kill switches notifications.release_events_enabled, notifications.fanout_enabled, notifications.ui_enabled, notifications.webhooks_enabled, notifications.web_push_enabled
Fanout tuning notifications.fanout.settle_seconds (default 30), notifications.fanout.max_series_burst (default 3)
Webhook guards notifications.webhooks.max_per_profile (10), notifications.webhooks.allow_private_destinations (false; dev only — label it loudly), notifications.webhooks.deliveries_per_minute_per_profile (60)
Retention notifications.retention.read_days (90), notifications.retention.unread_days (180), notifications.retention.event_days (30)

Files: add web/src/pages/admin-settings/NotificationsAdminSettings.tsx (follow EmailSettings.tsx / useSettingsForm), register in web/src/pages/admin-settings/AdminSettingsLayout.tsx. No backend work — all keys are live-read.

Effort: small (one page, no migrations, no Go changes).


2. Request-fulfilled notifications (request.fulfilled)

Why: 00-architecture-overview.md calls this "the most obvious next notification type." Users who request media currently learn it arrived by checking manually; every delivery channel they configured should tell them.

Design: the notification_deliveries.type registry is extensible by construction — no schema change.

  • New type request.fulfilled. reason_flags carries the operational shape (like webhook.auto_disabled does), e.g. {"request_id": "...", "tmdb_id": 123, "media_type": "movie"} — never the four reason booleans.
  • Hook point: the request reconciliation service (internal/mediarequests) is where a request transitions to available/fulfilled. On that transition, insert a delivery via DeliveryRepository.InsertOperational (the path the webhook auto-disable notice already uses) and publish through the system's dispatchers so websocket, web push, and webhooks all fire.
  • Recipient: the requesting profile (requests are profile-attributed). No profile_series_interest involvement — this is a direct, not fanned-out, notification.
  • Webhook enqueue: operational inserts bypass the fanout outbox, so either (a) extend InsertOperational to optionally enqueue per-target attempt rows, or (b) add a small shared "dispatch one delivery durably" helper used by both this and the auto-disable notice. Prefer (b); the auto-disable notice deliberately skips webhooks (loop guard) but request notices should not.
  • Per-reason preferences: add nothing in v1.5. The profile master toggle (notification_preferences.enabled) gates it; a dedicated notify_requests flag can come later if users ask.
  • Clients: the web inbox/toast/web-push renderers fall back to a generic card for unknown types; add a request.fulfilled case with the media title, poster, and a deep link to the item (or the request page until matched).

Effort: medium-small. The delivery/dispatch machinery all exists.


3. Email digest channel

Why: first real consumer of internal/mail; reaches users who don't keep a browser open and have no webhook.

Open design decisions (resolve before building):

  • Account-level, not profile-level. Email addresses live on users; profiles have none. A digest therefore aggregates across the account's profiles (group by profile inside the email body).
  • Digest, not per-episode. Per-episode email is spam at hundreds-of-users scale and duplicates the realtime channels. Recommend: opt-in daily digest of unread deliveries, sent by a taskmanager task (reuse the checkpointed iteration pattern from the interest backfill), with a per-user enable + cadence setting.
  • Unsubscribe / preference surface: account settings, not profile notification preferences.

Files (sketch): internal/notifications/email_digest.go (compose from DeliveryRepository, send via mail.Sender, branch on mail.ErrNotConfigured), a taskmanager task, a small user-settings surface.

Effort: medium. Blocked on the design decisions above, not on plumbing.


4. Native client adoption (no push required)

Why: the Android and Apple apps gain a full notification experience today — APNs/FCM only add closed-app wake-ups later.

Server surfaces ready for clients (silo-android, silo-apple):

  • GET /api/v1/notifications + unread-count + read endpoints — inbox UI.
  • GET /api/v1/notifications/sync — opaque forward cursor for reconnect/foreground catch-up (this is also the wake-fetch endpoint the v2 push specs assume, so client work done now is reused).
  • POST /api/v1/events/ws-ticket + ticket query param on /api/v1/events/ws, notifications channel — realtime while the app is open. Snapshot on subscribe hydrates recent unread.
  • GET /api/v1/notifications/capability — drive setup UI from this, never from admin settings.
  • GET/PUT /api/v1/notifications/preferences — per-profile reason toggles.

Effort: client-repo work; the server side is done. Coordinate per the multi-repo guidance in the repo root CLAUDE.md.


5. Hardening backlog (defer freely)

  • DB-backed integration tests from the 01 verification plan: idempotent availability/event inserts, cross-library delivery dedupe, per-series burst cap, outbox recovery (pending rows with no dispatch → retry worker sends), multi-node claim safety. The behaviors shipped and were exercised manually on dev; they are not yet pinned by automated tests because the repo has no Postgres test harness for this package.
  • Metrics: 01 names Prometheus-style counters (release_events_suppressed_total, etc.); v1 ships them as structured log fields. Revisit when the repo grows a metrics registry — keep the names.
  • Discord embed images via the media.discord-cdn-proxy service (see 04, "v1.5 payload"). Requires a new Silo-operated repo/service plus webhook_image_signer.go; v1 deliberately ships text-only embeds so the user's server origin never reaches Discord.
  • Webhook delivery history endpoint: webhook_delivery_attempts already has the listing index; a GET /api/v1/notifications/webhooks/{id}/attempts endpoint + UI table would make failures self-diagnosable beyond the last-failure summary.

Suggested order

  1. Admin settings UI (#1) — smallest, completes operability.
  2. Request-fulfilled (#2) — highest product value per effort.
  3. Webhook history endpoint (#5, last bullet) — pairs naturally with #1.
  4. Email digest (#3) — after its design decisions are made.
  5. Native clients (#4) — parallel track in the client repos.