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

# Notifizz Architecture Overview

> How an event flows from your backend SDK through the Notifizz pipeline — track API, queue, workflow instances, AI orchestrator, channel processors, real-time delivery.

# How Notifizz works

Notifizz is event-driven end to end. Your backend emits one event with a flat `track(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 to `POST /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](/docs/concepts/channels)).
* **Real-time front-end.** The widget receives new messages instantly — no polling.

## End-to-end pipeline

```mermaid theme={null}
sequenceDiagram
    participant Backend as Your backend
    participant API as Notifizz API
    participant Queue as Internal queue
    participant Workflow as Workflow instance
    participant NC as Notification Center

    Backend->>API: POST /v1/events/track
    API-->>Queue: enqueue
    Queue->>Workflow: create instance per matching campaign
    Workflow-->>Queue: AI orchestrator builds recipients
    Queue->>NC: dispatch per (recipient, channel)
    NC-->>Backend: 200 (event was accepted earlier)
```

## Each stage

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

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

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

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

  <Step title="Enqueue per-recipient sends">
    Once the orchestrator produces a recipient list, the system enqueues one job per (recipient, channel) pair.
  </Step>

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

  <Step title="Real-time client delivery">
    The widget receives new messages in real time — the bell badge updates the moment the notification commits, with no polling and no SSE plumbing on your side.
  </Step>
</Steps>

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

The SDK call site never picks a campaign, channel, or recipient list. That separation is what makes the marketing/dev split workable: marketing edits campaigns without redeploying code, and the same event drives multiple campaigns over time.

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

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

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

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

  <Accordion title="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](/docs/concepts/recipients) for the patterns.
  </Accordion>

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

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

## See also

<CardGroup cols={2}>
  <Card title="Events and workflows" icon="diagram-project" href="/docs/concepts/events-and-workflows">
    Events, properties, campaigns, recipients — the building blocks.
  </Card>

  <Card title="Channels" icon="bell" href="/docs/concepts/channels">
    In-app, email, and what's on the roadmap.
  </Card>

  <Card title="Authentication" icon="key" href="/docs/concepts/authentication">
    Widget auth modes — Firebase, backend token, publicly-signed JWT, none.
  </Card>

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