# Components
Here’s the whole system as a set of Lego bricks. You usually only touch one or two of them.
flowchart TB
subgraph apps [Your applications]
UI["Bell / inbox UI"]
Server["Backend / jobs"]
end
subgraph npm [Published package]
Client["@inovus-medical/notifications"]
React["/react hooks"]
end
subgraph service [This repo]
API["packages/api — Cloudflare Worker"]
Core["packages/core — shared types"]
Store["NotificationStore adapters"]
end
DB[(Supabase Postgres)]
Cognito[AWS Cognito JWKS]
UI --> Client
Client --> React
Client -->|HTTPS + JWT| API
Server -->|HTTPS + M2M JWT| API
Server --> Client
API --> Core
Client --> Core
API --> Store
Store --> DB
API --> Cognito# 1. The API (packages/api)
A small Cloudflare Worker that speaks HTTP.
- Verifies Cognito (OIDC) JWTs
- Enforces who can publish vs who can only read their inbox
- Talks to the database through a store interface
- Serves OpenAPI docs at
/docsand/openapi.json - Also serves the legacy V1
/api/Notification/*routes for older callers
Production URL: https://notifications.totumcloud.com
You don’t have to deploy this yourself if you’re integrating with the shared Inovus instance — just point the client at that base URL.
# 2. The npm client (packages/client)
Package name: @inovus-medical/notifications
This is what most app developers live in day to day:
| Export | Purpose |
|---|---|
NotificationsClient |
List, subscribe, mark read, publish, delete… |
createNotificationService |
V1-shaped notification.success(title, body, url?) helpers |
NotificationsApiError |
Typed errors with problem+json details |
@inovus-medical/notifications/react |
useNotifications(client) hook |
Zero runtime dependencies. React is an optional peer — only needed if you use the /react entry.
# 3. Shared types (packages/core)
Private package of Zod schemas + TypeScript types. It is the single source of truth for the wire format (camelCase JSON).
- API validates requests with these schemas
- OpenAPI is generated from them
- Client types stay in lockstep (bundled into the published client)
You rarely import @inovus-medical/notifications-core directly — the client re-exports the types you need.
# 4. The database (Supabase / Postgres)
One notifications table. Important properties:
- Keyed by the user’s Cognito
sub(user_guid) - Race-safe unique index for dedupe:
(user_guid, source, dedupe_key) - RLS enabled with no policies (deny-all) — only the Worker service role touches data
- Retention cron purges old rows (default 90 days)
There is also an in-memory store for local dev and tests (wiped on restart).
# 5. Identity (Cognito)
We don’t invent accounts. We verify the JWTs you already issue:
- User tokens → inbox ownership via
sub - M2M (client credentials) tokens → publish via scope
notifications/publish - Optional publisher groups for human/admin tokens
source on each notification is taken from the token (client_id or sub) — callers cannot spoof another producer.
# 6. What you still build
This service is deliberately headless:
| You build | We provide |
|---|---|
| Bell icon + dropdown | items, unreadCount, mark/delete APIs |
| Toast banners | — (out of scope) |
| Router navigation on click | actionUrl field on each notification |
| Cognito login / token refresh | getAccessToken callback you inject |
# How the pieces show up in a typical app
// 1. Create the client once (token wiring is yours)
const client = new NotificationsClient({
baseUrl: 'https://notifications.totumcloud.com',
getAccessToken: () => auth.getValidAccessToken(),
});
// 2. Optional: V1-shaped helpers for self-notifications
export const notification = createNotificationService(client);
// 3. Optional: React hook for the bell
const { notifications, unreadCount, markRead } = useNotifications(client);
Next: Why V2? if you’re migrating, or jump to Getting started.