Skip to main content

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.
  • In a multi-message sequence, recipients are either recalculated each send (default) or locked to those of the first send — a per-campaign setting on the Recipients card.

Events

An event is a single fact about your application. You name it, you attach properties, you emit it.
The shape is intentionally flat — (eventName, properties, options). The SDK never references workflows, campaigns, or recipients.

Naming events

Events follow a domain.event_name shape — for example order.shipped, user.signed_up, invoice.paid: the domain says where the fact comes from, the name says what happened, in the past tense. 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:
  1. Recipient identification — the orchestrator extracts userId, email, or whatever your campaign keys on, directly or via an enricher.
  2. Template substitution{{ trackingUrl }} in the notification content resolves to the property.
  3. Routing context — campaigns can branch on properties (e.g. send a different notification when plan === "pro").
Pass enough context for the campaign to do its job, but don’t dump your whole DB row — properties are persisted on the workflow instance for traceability.

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:
A retried emit with the same key short-circuits at the backend ({ 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 / Implementation / Review / Live / Offline / Archived) that controls whether it runs, and in which environment.
Crucially, the SDK call site never references the campaign. You don’t pass an id, you don’t list recipients. When 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 move through six statuses — Editing, Implementation, Review, Live, Offline, Archived — and the runtime gate is per-environment: Review sends on non-production environments only, Live sends for real, Archived is terminal. The full status table, runnable filter, and version semantics are in campaigns concept. 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:
Additional fields can be attached for use in templates (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).
  • Reach an audience the event never names — every admin of a team, every watcher of a document — with an enricher that returns a list (recipients).
Pick the one that matches the freshness and coverage you need. For a mailing that no user action triggers at all, see broadcasts.

Sequences: who receives the follow-up sends

A campaign can carry several messages spread over time — day 0, day 1, day 10. At every send, the orchestrator runs again and resolves the recipients live. That raises one question the platform makes explicit: should someone who enters your audience between two sends receive the follow-ups? The answer is a setting on the Recipients card, with two values: Pick by intent, not by mechanism:
  • An invoice reminder sequence (day 3, day 10, day 30 after invoice.issued) wants recalculated each send: if the billing contact changed between two reminders, the reminder must reach the current contact — the sequence is about the invoice, not about a person.
  • A three-message activation sequence (welcome, tips, upgrade nudge) wants from the first send: message three makes no sense for someone who never saw message one. A user who signs up on day 9 shouldn’t land in the middle of a story that started without them.
Two behaviours of from the first send are worth stating bluntly, because they surprise:
  1. The setting blocks arrivals, it doesn’t compensate departures. Recipients who appear after the first send don’t receive the rest — and recipients who left your audience in the meantime are not replaced. It’s normal for the second send to reach fewer people than the first; the per-send statistics show the difference, and excluded latecomers appear under their own label, “Not in the first send”.
  2. Your identifier is what makes someone the same person. Notifizz holds no customer data — the sequence follows each recipient by the identifier your application returns at each send. That’s the direct counterpart of being privacy friendly: a platform that resolves identity across changes can only do it by holding your user base. See how recipients are recognised.
One structural rule comes with from the first send: the first message of the sequence cannot carry an “Only if…” filter. Its recipients define the whole sequence’s recipients, so filtering it would silently shrink every following send. The editor doesn’t offer the filter on the first message under this setting, and switching to the setting removes an existing one — explicitly, with a warning. Move the filter to a later message instead.

Where each piece lives

That column-three / column-two split is the core invariant: the SDK is data-only, the dashboard owns intent and routing.

FAQ

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.
They keep running against the version they started on. New events trigger the new version. Versioning prevents mid-flight surprises.
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.
No. Each campaign creates its own workflow instance, runs its own orchestrator, and dispatches independently. They share nothing beyond the inbound event payload.
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.
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.
Expected under From the first send when people left your audience between the two sends: the setting blocks arrivals, it doesn’t replace departures. The per-send statistics show the difference, and excluded latecomers are counted under “Not in the first send”.
Check that your application returns the same identifier for that person at every send. The sequence follows recipients by the identifier you provide — if it changes mid-sequence (say, an email replaced by an internal id), the person is no longer recognised as the same recipient. See how recipients are recognised.
Your first message carries an “Only if…” filter — and under this setting, the first message defines the recipients of the whole sequence, so it cannot be filtered. Move the filter to a later message.

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.