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.

config.nucleus.json — notification
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}
channelsobjectOptional

The set of enabled delivery channels.

portalbooleanOptional

In-app notifications surfaced via the NotificationCenter component.

Defaulttrue
emailbooleanOptional

Email delivery — uses the top-level email block (Gmail or Azure) as transport.

Defaultfalse
sms{ enabled?: boolean; provider?: string }Optional

SMS delivery through the named provider.

telegram{ enabled?: boolean; botToken?: string }Optional

Telegram delivery via a bot. botToken takes an env-var name holding the token.

webhook{ enabled?: boolean; url?: string }Optional

POST 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'Optional

The channel used when a notify call doesn't specify one. Most apps keep this on portal and opt into louder channels per message.

Default'portal'
templateVariablesRecord<string, string>Optional

Global 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.

Default{}

Endpoints#

The REST surface clients use to read and manage notifications.

endpoints{ enabled?: boolean; basePath?: string }Optional

List, mark-as-read and manage in-app notifications. The NotificationCenter component talks to these routes.

Default{ 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-outOptional

When 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_verifiersOptional

A 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 tableOptional

A 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 deliveryEmailServiceOptional

An 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.

components/NotificationBell.tsx
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 · /notifications

Paginated in-app notifications with optional limit, offset and type filter.

NOTIFICATION_UNSEEN_COUNTGET · /notifications/unseen-count

The unread count — drive the bell badge from this.

NOTIFICATION_MARK_SEENPOST · /notifications/:notification_id/seen

Mark a single notification as read.

NOTIFICATION_MARK_ALL_SEENPOST · /notifications/seen-all

Mark every notification as read at once.

Related sections