Notification event design
Events and campaigns are the two surfaces a Notifizz user touches. Together they define what happened, what to do about it, and who it concerns — but each lives in a different place.TL;DR
- An event is what your code emits with
client.track(eventName, properties). No campaign id, no recipient list. - A campaign is a dashboard-defined reaction to an event — its orchestrator code, channels, and content.
- Properties ride on the event; the campaign’s orchestrator (and any registered enricher) turn them into recipients and template values.
- The same event can drive multiple campaigns without code changes.
Events
An event is a single fact about your application. You name it, you attach properties, you emit it.(eventName, properties, options). The SDK never references workflows, campaigns, or recipients.
Naming events
Events follow a domain.event_name shape — for exampleorder.shipped, user.signed_up, invoice.paid. The grammar is Stable names matter: campaigns key on them, and renaming an event after a campaign ships breaks the wire-up.
Properties
Properties are arbitrary JSON-serialisable data — strings, numbers, booleans, nested objects. They serve three jobs:- Recipient identification — the orchestrator extracts
userId,email, or whatever your campaign keys on, directly or via an enricher. - Template substitution —
{{ trackingUrl }}in the notification content resolves to the property. - Routing context — campaigns can branch on properties (e.g. send a different notification when
plan === "pro").
Idempotency
Every event carries an idempotency key. Auto-generated UUIDs are fine for one-off emits; for retried jobs, set a deterministic key derived from your domain:{ duplicate: true }). See Event Tracking reference.
Campaigns
A campaign (the term workflow is used interchangeably in older docs and in some backend code paths) is a dashboard-configured reaction to an event. It owns:- The event name it listens on.
- An orchestrator — generated code that builds the recipient list from event properties + enrichers.
- One or more channels with their templates (Notification Center config, email config, …).
- A status (
Editing/Dev/Live/Offline/Archived) that controls whether it runs in which environment.
order.shipped fires, every campaign listening on order.shipped runs its own orchestrator independently — fan-out is automatic.
One event, many campaigns
This is the common pattern: a single product event drives several distinct notifications. Each campaign decides its own recipients (from event properties or an enricher), its own channel, its own template. Adding a new campaign is a dashboard task — no code change.Versioning and lifecycle
Campaigns have five statuses (Editing, Dev, Live, Offline, Archived) and the runtime gate is per-environment — Dev runs on dev only, Archived is terminal. The full status table, runnable filter, and version semantics are in campaigns concept (ADR-grounded).
Each save creates a new version — in-flight workflow instances run against the version they started on, even if a newer version has shipped since.
Recipients
Recipients are produced by the campaign’s orchestrator, not by the SDK call site. The orchestrator runs server-side, reads the event properties, and returns a list of recipient objects. Each must have at minimum:displayName, locale, timezone, …). The orchestrator can:
- Extract recipients directly from event properties —
{ userId, email }baked into the event. - Resolve them via an enricher — a server-side function the orchestrator calls to fetch user data live (enrichers tutorial).
- Pull from a dynamic source — saved audiences, segments (dynamic recipient sources plan).
Where each piece lives
| Concept | Where it’s defined | Surface |
|---|---|---|
| Event | Your backend code | client.track(eventName, properties) |
| Campaign | Notifizz dashboard | Orchestrator + channels + status |
| Properties | Your backend code | Passed to track() |
| Recipients | Campaign’s orchestrator (server-side) | Output of orchestrator code |
FAQ
Can I send to a specific user from the SDK?
Can I send to a specific user from the SDK?
No — that’s the deliberate constraint. Put
userId in the event properties and let the campaign’s orchestrator return that user as the only recipient. If you find yourself wanting “send this exact notif to this exact user”, model it as a campaign listening on a dedicated event.What happens to in-flight workflow instances when I edit a campaign?
What happens to in-flight workflow instances when I edit a campaign?
They keep running against the version they started on. New events trigger the new version. Versioning prevents mid-flight surprises.
I renamed an event — does the old campaign still work?
I renamed an event — does the old campaign still work?
No — the campaign keys on the event name. Either revert the rename in your code, or update the campaign in the dashboard to listen on the new name. There is no auto-redirect.
Can two campaigns on the same event interfere?
Can two campaigns on the same event interfere?
No. Each campaign creates its own workflow instance, runs its own orchestrator, and dispatches independently. They share nothing beyond the inbound event payload.
How do I model 'cart abandoned after 30 minutes'?
How do I model 'cart abandoned after 30 minutes'?
Two options: (1) emit
cart.abandoned from your backend after a server-side timer; or (2) emit cart.created and let the campaign’s orchestrator schedule the delayed step. Option (1) keeps timing logic in your domain; option (2) keeps everything in Notifizz. Both are common.What does 'transactional' vs 'marketing' mean here?
What does 'transactional' vs 'marketing' mean here?
Same primitive. A transactional event (invoice paid, order shipped) and a marketing event (we miss you) both flow through
track(). Priority is decided per campaign — transactional campaigns jump the queue.See also
How Notifizz works
The event-driven pipeline, end to end.
Channels
Notification Center widget, email, what’s next.
Event Tracking reference
HTTP wire format, idempotency contract.
Backend quickstart
Send your first event in under five minutes.