Notification
Portal, email, SMS, Telegram & webhook channels
The notification service is a single API your app calls to reach a user, fanning the message out across the channels you've enabled. Today it actually delivers two: portal (an in-app record) and email (through your email block). The sms, telegram and webhook channels are first-class in the config and routing, but their senders are still stubs — selecting them currently logs rather than delivers, so treat them as planned surfaces.
Messages are templated: any {{placeholder}} in the content is replaced from the call's context first, then from your global templateVariables. This keeps boilerplate (brand name, support URL) in one place.
Several framework features (new-device alerts, verification outcomes) emit notifications through this service, so enabling it improves the whole product, not just your own calls.
Channels#
Switch on the delivery surfaces you want. portal and email are simple booleans; the others nest their own credentials so the service can actually connect.
1{2 "notification": {3 "enabled": true,4 "channels": {5 "portal": true,6 "email": true,7 "sms": { "enabled": false, "provider": "twilio" },8 "telegram": { "enabled": false, "botToken": "TELEGRAM_BOT_TOKEN" },9 "webhook": { "enabled": false, "url": "SLACK_WEBHOOK_URL" }10 },11 "defaultChannel": "portal",12 "templateVariables": {13 "appName": "Acme",14 "supportUrl": "https://acme.com/support"15 },16 "endpoints": { "enabled": true, "basePath": "/notifications" }17 }18}channelsobjectOptionalThe set of enabled delivery channels.
portalbooleanOptionalIn-app notifications surfaced via the NotificationCenter component.
trueemailbooleanOptionalEmail delivery — uses the top-level email block (Gmail or Azure) as transport.
falsesms{ enabled?: boolean; provider?: string }OptionalSMS delivery through the named provider.
telegram{ enabled?: boolean; botToken?: string }OptionalTelegram delivery via a bot. botToken takes an env-var name holding the token.
webhook{ enabled?: boolean; url?: string }OptionalPOST the notification payload to an external URL — bridge into Slack, Discord or your own systems.
Routing & templates#
Pick the default destination and define reusable substitution variables shared by every template.
defaultChannel'portal' | 'email' | 'sms' | 'telegram' | 'webhook'OptionalThe channel used when a notify call doesn't specify one. Most apps keep this on portal and opt into louder channels per message.
'portal'templateVariablesRecord<string, string>OptionalGlobal key→value pairs interpolated into every template. Call-time context wins for overlapping keys, then these fill the rest. Ideal for {{appName}}, {{supportUrl}} and the like.
{}Endpoints#
The REST surface clients use to read and manage notifications.
endpoints{ enabled?: boolean; basePath?: string }OptionalList, mark-as-read and manage in-app notifications. The NotificationCenter component talks to these routes.
{ enabled: true, basePath: "/notifications" }Under the hood — NotificationService#
Beyond the simple send API, the service is the delivery half of the verification engine: it turns a flow's notification rules into actual messages for the right people.
triggerNotificationsrule-driven fan-outOptionalWhen a verification flow fires on_flow_started / on_step_reached / on_decision, the service loads the matching notification rules (filtered by node, and by a startsAt/expiresAt window), interpolates their title/body templates and sends to every resolved recipient on the rule's enabled channels.
recipient resolutionuser · role · step_verifier · entity_creator · all_verifiersOptionalA rule's recipients can be concrete users, a role (expanded to all its users), the current step's verifier, the entity's creator (the instance's startedBy), or every verifier in the flow — all de-duplicated into a final user-id set before sending.
portal deliverynotifications tableOptionalA portal notification is a row in the notifications table (user_id, title, body, entity_name, entity_id, type, source, is_seen) — exactly what NOTIFICATION_LIST / UNSEEN_COUNT / MARK_SEEN read and what the NotificationBell renders.
email deliveryEmailServiceOptionalAn email notification looks up the recipient's address and hands a subject/html to the shared EmailService — so it inherits whichever provider (Gmail or Azure) the email block configures, and silently no-ops if email isn't available.
From the frontend#
Turning endpoints on generates these ready-made, type-safe actions on the useApiActions hook — the portal's notification bell and center are built entirely from them. No fetch code required.
1const actions = useApiActions();2 3// unread badge4actions.NOTIFICATION_UNSEEN_COUNT.start({5 onAfterHandle: (r) => setCount(r.data.count),6});7 8// list + mark read9actions.NOTIFICATION_LIST.start({ payload: { limit: 20 } });10const markRead = (id: string) =>11 actions.NOTIFICATION_MARK_SEEN.start({ payload: { notification_id: id } });NOTIFICATION_LISTGET · /notificationsPaginated in-app notifications with optional limit, offset and type filter.
NOTIFICATION_UNSEEN_COUNTGET · /notifications/unseen-countThe unread count — drive the bell badge from this.
NOTIFICATION_MARK_SEENPOST · /notifications/:notification_id/seenMark a single notification as read.
NOTIFICATION_MARK_ALL_SEENPOST · /notifications/seen-allMark every notification as read at once.
Related sections