# Roadmap — what V3 onwards might include
Planning document for this internal notification service, not a delivery commitment and not a product we sell externally.
It is shared infrastructure for Inovus apps (Totum / Video Library, Assess, Review Tool, and whatever else adopts it). The question is only: what might our own products need next? Ideas below are informed by how mature notification systems are usually structured — useful as a checklist of needs, not a feature race against third-party vendors.
For architecture of the current system, see DESIGN.md. For resilience options already sketched, see High availability.
# Where we are (V2)
V2 is a standalone in-app notification inbox used by our applications:
| Capability | Status |
|---|---|
| Server/API-key publish to a user inbox | Done |
| User list / unread / mark read / delete | Done |
Idempotent publish (dedupeKey) |
Done |
| OIDC auth + publisher scopes / API keys | Done |
| Typed npm client (retry, ETag polling, React hooks) | Done |
| Legacy product V1 HTTP compatibility | Done (/api/Notification/*) |
| Email, SMS, push, chat (Slack/Teams) | Out of scope |
| User preferences / mute / digest | Out of scope |
| Realtime push (WebSocket / SSE) | Out of scope (polling + ETag) |
| Shared bell UI / preference centre | Out of scope (each app owns UI) |
| Workflows, templates, scheduled digests | Out of scope |
That is intentional. V2 extracted and hardened the inbox that used to live inside Video Library (auth, dedupe, standalone Worker, reliable client). It is the shared in-app feed + publish API for our estate — not a multi-channel engagement product and not something we intend to commercialise.
Current HTTP surface: /v2/notifications…. A future major API would likely live under /v3 (or stay compatible and add capabilities without a break) — naming is TBD when the first breaking change is real.
# What a fuller notification system usually covers
When teams grow an internal inbox service, needs tend to split into two layers:
┌─────────────────────────────────────────────────────────────┐
│ Our service (orchestration + inbox) │
│ decide who gets what · preferences · digests · in-app feed │
│ optional fan-out to other channels · delivery status │
└───────────────────────────┬─────────────────────────────────┘
│ call out to (if we ever need them)
┌───────────────────────────▼─────────────────────────────────┐
│ Channel providers we already pay for / could plug in │
│ Email (e.g. SES) · SMS · FCM/APNs · Slack / Teams webhooks │
└─────────────────────────────────────────────────────────────┘
Capabilities that often show up once an organisation has more than one product publishing notifications:
- One publish path — apps emit an event; the service applies policy (preferences, channels).
- Preferences — mute categories, quiet hours, choose email vs in-app only.
- Extra channels — email (most common), then push or staff chat when people aren’t in the SPA.
- Digests / delay-and-cancel — stop spamming; remind only if still unread.
- Templates — shared copy for email/push so each app doesn’t reinvent wording.
- Delivery visibility — support can answer “did it send / bounce?”
- Optional shared UI — one bell/preference panel reused across Totum apps.
We only build these when an Inovus product has a concrete need. Marketing-style campaigns, segments, and growth analytics are not in scope for this service.
# What we have vs what we might need
| Need | V2 today | Worth considering later? |
|---|---|---|
| In-app inbox CRUD | Yes | Extend (archive, categories) if apps ask |
| Idempotent / deduped events | Yes (dedupeKey) |
Keep as a hard invariant |
| Email / push / chat delivery | No | Only if a product needs reach outside the SPA |
| User preferences / quiet hours | No | Yes — before any second channel |
| Digests / delay / escalate | No | If several apps reinvent the same logic |
| Faster-than-poll inbox updates | Polling + ETag | If UX measures require it |
| Shared bell / preference UI | Hooks only | If two+ apps duplicate UI, or toast/inbox visuals diverge |
| Email/push templates | No | With channels, if copy should be central |
| Delivery timeline for support | Logs only | If support can’t answer from existing logs |
| Batch publish | Loop + dedupe | If fan-out volume hurts |
| Stronger org/tenant isolation | Multi-issuer; one DB | If business units need harder separation |
# Guiding principles
- Internal shared service — designed for Inovus apps and operators, not external customers of a notification SaaS.
- Stay the system of record for the inbox — if we add email/push, the in-app row should still record what the user was told, unless we deliberately split that.
- Preferences before channels — outbound email without mute/category controls creates noise and support load (especially in healthcare-adjacent products).
- Reuse existing pipes — call SES / FCM / Slack webhooks etc.; don’t build SMTP or push infrastructure ourselves.
- Demand-driven — DESIGN.md’s YAGNI list still applies; name the app and the pain before scheduling work.
- Right-sized — solve our product problems; ignore feature checklists from commercial notification vendors.
# Candidate themes (V3 onwards)
Ordered by how often our apps are likely to hit them, not by a fixed release train. Many can ship as additive V2.x; only breaking contract changes need a hard /v3.
# 1. Richer inbox semantics
Why: Apps outgrow binary read/unread.
Possible additions:
- Categories / topics (
assessment,system,billing) for filtering and preferences - Archive / snooze (soft state; retention still applies)
- Severity-driven “needs attention” views
- Clearer actor / subject fields beyond opaque
metadata - Per-notification expiry (in addition to global retention)
Trigger: Multiple apps need shared filters, or support asks for “hide without delete”.
# 2. User preferences and quiet hours
Why: Needed before anything leaves the in-app feed; also useful to mute noisy in-app categories.
Possible model:
- Per user: global mute, per-category channel matrix (in-app / email / push)
- Quiet hours + timezone
- Defaults per
sourceor category (e.g. security alerts cannot be fully muted)
Trigger: Before first outbound email/SMS/push goes live — or when users complain about inbox noise.
# 3. Extra channels (email first)
Why: Users aren’t always in the SPA; some workflows need email or mobile push.
Possible shape:
Publish event
→ create/update in-app notification (if preference allows)
→ enqueue channel jobs (email / push / …) honouring preferences
→ provider adapters + retries + dead-letter
Sensible internal order:
| Phase | Channel | When it might matter |
|---|---|---|
| A | Transactional email | “Email me when X” from a product owner, with real volume |
| B | Web / mobile push | Native or PWA apps need OS notifications |
| C | Slack / Teams | Staff tools where people already live in chat |
| D | SMS | High-urgency only — cost and consent overhead |
Even for email-only, plan for delivery status (queued / sent / bounced) so support isn’t blind.
Trigger: A named product requires reach outside the browser session.
# 4. Digests, delay, and cancellation
Why: Stops producers reinventing “remind later / don’t spam” in every app.
Examples that fit our domain:
- Digest: “N new assessment comments in the last hour” → one inbox item (+ email if enabled)
- Delay + cancel: remind in 24h unless the case was already opened
- Escalate: in-app first; email if still unread after N hours
- Fan-out: one domain event → many recipients (batch API or queue)
Prefer logic in the producer until several apps copy the same pattern; only then consider shared digest/delay primitives in this service.
Trigger: Three or more producers re-implement the same delay/digest/escalate behaviour.
# 5. Realtime delivery
Why: Polling is proven and cheap; some screens may want a faster bell.
Options already noted in DESIGN.md / HA docs:
- SSE or WebSocket via Cloudflare Durable Objects (or similar), behind the existing client
subscribe()API - Avoid coupling to Supabase Realtime unless we accept Cognito≠Supabase JWT constraints
Trigger: Measured UX need for low-latency updates — not “realtime for its own sake”.
# 6. Shared client UI (optional)
Why: If every Totum app builds its own bell — and a divergent toast look — we pay the cost repeatedly and the product feels inconsistent.
Possible direction:
- Keep headless hooks as the default
- Optional shared package for bell, feed, empty states, preference panel — and presentational primitives reusable by each app’s toast host
- Better multi-tab sync (BroadcastChannel) and visibility-aware polling
Trigger: Two or more apps are copy-pasting the same notification UI, or toast and inbox look like different products.
# 7. Publish throughput and producer helpers
Why: Fan-out and spikes should not force every app to invent queues.
Possible additions:
- Batch publish endpoint for many recipients
- Queue-backed publish (e.g. Cloudflare Queues) for spikes and channel fan-out
- Cancel/replace in-flight digests by key
- Richer OpenAPI examples for any new payloads
Trigger: Sustained publish rate or fan-out size that makes per-row HTTP loops painful (DESIGN.md already cites ~>10/sec sustained as a revisit point).
# 8. Observability and support tooling
Why: Internal ops will ask “did the user get it?”
Possible additions:
- Per-notification timeline (inbox created, email queued/sent/bounced)
- Audited support lookup by
userGuid+dedupeKey - Basic metrics: publish rate, unread age, channel failures
- Correlation IDs from producer → inbox → channel send
Trigger: Support or engineering cannot answer delivery questions from Worker/Supabase logs alone.
# 9. Stronger isolation and compliance
Why: As more business units share the service, isolation and audit may need to harden.
Possible additions:
- Explicit org/tenant dimension on notifications and preferences
- Clearer staging vs prod separation (already partly true via Worker envs)
- Retention / residency rules if required
- Stronger audit trails for publish and preference changes
- Whatever Inovus needs for SOC2 / clinical safety narratives
Trigger: A second business unit needs isolation beyond “different Cognito issuers”, or compliance asks for stronger evidence.
# Suggested sequencing (if we grow beyond the inbox)
A pragmatic order for our estate:
1. Categories + preferences (+ quiet hours)
2. Email + delivery status (transactional only)
3. Digests / delay-and-cancel for the noisiest producers
4. Shared UI only if apps are duplicating bells
5. Faster subscribe transport if UX requires it
6. Push / chat only with a named product need
7. Shared workflow primitives only if producers keep reinventing them
8. Deeper tenancy / compliance when org structure requires it
Skip or reorder freely. A thin batch publish or SSE slice can jump the queue if one producer is blocked.
# Explicit non-goals
- Selling or operating this as an external notification product
- Company-wide marketing automation (newsletters, promotional journeys, ad audiences)
- Building and running email/SMS/push provider infrastructure ourselves
- Mobile push without the relevant app team owning FCM/APNs credentials and consent UX
- Matching any commercial vendor’s full feature list
# How to use this document
| Audience | Use |
|---|---|
| Product | Prioritise against real pain in our apps; don’t schedule channels before preferences |
| Engineering | Option sketches only — still need DESIGN updates (+ changesets if the npm client changes) |
| Agents / implementers | Do not build from this file alone — wait for an explicit implementation brief |
When a theme is green-lit, update DESIGN.md (scope + trade-offs), add acceptance criteria, and decide whether the HTTP API stays on /v2 (additive) or needs a /v3 break.
# Related reading
- DESIGN.md — current V2 architecture and explicit out-of-scope list
- Why V2 — why the standalone inbox exists
- High availability — resilience options that overlap with scale/realtime work
- App integration — how producers and SPAs use V2 today