How Notifizz works
Notifizz is event-driven end to end. Your backend emits one event with a flattrack(eventName, properties) call — even an incomplete one. The platform resolves campaigns, the AI orchestrator enriches the event with your live business data and builds the recipient list, and notifications dispatch through every channel — all server-side, with no workflow or recipient targeting at the call site.
TL;DR
- One call, one event.
client.track(eventName, properties)posts toPOST /v1/events/track. - Server-side routing. Campaigns matching the event name are looked up; one workflow instance is created per match.
- AI orchestrator builds recipients. The campaign’s orchestrator runs against the event properties, calls enrichers to fetch live data, and produces the recipient list. The AI surfaces dev tasks for any gap (missing event property, undefined enricher) — one-click executable from your IDE via MCP.
- Channels dispatch. A queue processor delivers the resolved content per channel — Notification Center widget today, email next (roadmap).
- Real-time front-end. The widget receives new messages instantly — no polling.
End-to-end pipeline
Each stage
Track the event
Your backend calls
client.track(eventName, properties, { idempotencyKey }). The SDK posts to POST /v1/events/track with the SDK secret key as Bearer + X-Idempotency-Key. The backend acks acceptance within milliseconds — everything below runs asynchronously.Match campaigns
The event handler looks up campaigns wired to this
eventName in the caller’s environment. Idempotency is enforced here: a duplicate idempotencyKey returns { duplicate: true } and skips re-enqueue.Create workflow instances
One workflow instance is created per matching campaign. Each instance carries the event properties, a correlation id, and the campaign version it ran against — so in-flight notifications run against the campaign version they started on, even if a newer version has shipped since.
Run the AI orchestrator
The orchestrator runs the campaign’s code: enrichment (calling registered enrichers via HMAC-signed webhooks to fetch live data your event didn’t carry), recipient resolution, branching. The AI generates this code from the campaign description; you review, edit, ship.
Enqueue per-recipient sends
Once the orchestrator produces a recipient list, the system enqueues one job per (recipient, channel) pair.
Dispatch through channels
The notifications processor calls each channel: for the Notification Center, the message is delivered to the user’s inbox + the unread index is bumped; email lands next.
What you control
| Surface | What you decide |
|---|---|
| Backend | What events fire, when, with which properties + idempotency keys. |
| Dashboard | Which campaigns exist, what they listen on, their orchestrator (AI-generated, you review), channel configs, recipient logic. |
| Frontend | Where the bell renders, custom bell UI, headless mode, optional in-app reactions to state changes. |
Idempotency, retries, traceability
- Idempotency — every
track()carries a key (auto-UUID or caller-supplied). Duplicate keys short-circuit at the backend with{ duplicate: true }. - Retries — the SDK retries transient failures twice (1s, 2s) before bubbling. Once the backend accepts the event, the internal queue owns retries downstream — failed steps land in the dead-letter queue with a correlation id you can trace.
- Correlation ID — stamped at message creation time, propagates through every queue hop and every delivery write. Use it to follow a single delivery end-to-end.
FAQ
Why server-side routing instead of `track().workflow(slug, recipients)` like before?
Why server-side routing instead of `track().workflow(slug, recipients)` like before?
The chained shape forced the developer to know the campaign slug and the recipient list at the call site — two things that almost always change before the campaign ships. With server-side routing, marketing edits the campaign without a deploy, and the same event drives multiple campaigns over time.
What happens if no campaign matches my event?
What happens if no campaign matches my event?
The event is accepted (
200) and recorded, but no workflow instance is created and no notification fires. This is intentional — you can register events ahead of building campaigns. The dashboard’s “missing properties” / “unrouted events” view surfaces these so you can wire up a campaign later.My event fired but the campaign didn't run.
My event fired but the campaign didn't run.
Common causes in order: (1) campaign status is
Editing (only Live runs on prod; Dev runs on dev environments); (2) event-name mismatch (whitespace, case, source vs target); (3) campaign listening on the wrong environment. Check the dashboard delivery history — campaigns that didn’t match show a no-route entry.How does the orchestrator know who to notify?
How does the orchestrator know who to notify?
The orchestrator is code (generated by the AI from the campaign description, editable). It reads the event properties, calls registered enrichers if needed (e.g.
fetchUser({ userId })), and returns a recipient list. Each recipient must have at minimum id + email. See recipients for the patterns.Is there latency between `track()` and the user seeing the bell update?
Is there latency between `track()` and the user seeing the bell update?
Acceptance is millisecond-fast. End-to-end latency depends on orchestrator complexity (especially enricher round trips) and queue depth. In normal operation, simple campaigns deliver in under a second; campaigns with cold enrichers can take a few seconds.
How does the widget update in real time?
How does the widget update in real time?
The widget subscribes to the user’s inbox over a real-time stream — new writes propagate instantly to the dropdown and badge, with no SSE plumbing or polling on your side. Reconnection and offline queueing are handled for you.
See also
Events and workflows
Events, properties, campaigns, recipients — the building blocks.
Channels
In-app, email, and what’s on the roadmap.
Authentication
Widget auth modes — Firebase, backend token, publicly-signed JWT, none.
Backend quickstart
Send your first event in under five minutes.