> ## Documentation Index
> Fetch the complete documentation index at: https://notifizz.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Notification Event Design

> Events, properties, campaigns, and recipients — the four building blocks of a Notifizz pipeline. Designed for event-driven campaigns with no client-side targeting.

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

```javascript theme={null}
await client.track("order.shipped", {
  orderId: "ORD-4521",
  userId: "u_42",
  carrier: "FedEx",
  trackingUrl: "https://tracking.example.com/4521",
});
```

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 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:

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:

```javascript theme={null}
await client.track(
  "order.shipped",
  { orderId, userId },
  { idempotencyKey: `order-shipped:${orderId}` },
);
```

A retried emit with the same key short-circuits at the backend (`{ duplicate: true }`). See [Event Tracking reference](/docs/sdks/event-tracking/overview).

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

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.

```mermaid theme={null}
graph LR
    Event[order.shipped] --> CampaignA[Customer shipping email]
    Event --> CampaignB[In-app delivery alert]
    Event --> CampaignC[Ops Slack — late shipments]
```

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](/docs/concepts/campaigns) (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:

```javascript theme={null}
{ id: "u_42", email: "alice@example.com" }
```

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](/docs/sdks/how-to/enrichers-tutorial)).
* Pull from a **dynamic source** — saved audiences, segments (dynamic recipient sources plan).

Three patterns; pick the one that matches the freshness and coverage you need.

## 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           |

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

## FAQ

<AccordionGroup>
  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>
</AccordionGroup>

## See also

<CardGroup cols={2}>
  <Card title="How Notifizz works" icon="diagram-project" href="/docs/concepts/how-it-works">
    The event-driven pipeline, end to end.
  </Card>

  <Card title="Channels" icon="bell" href="/docs/concepts/channels">
    Notification Center widget, email, what's next.
  </Card>

  <Card title="Event Tracking reference" icon="code" href="/docs/sdks/event-tracking/overview">
    HTTP wire format, idempotency contract.
  </Card>

  <Card title="Backend quickstart" icon="rocket" href="/docs/quickstart/backend">
    Send your first event in under five minutes.
  </Card>
</CardGroup>
