> ## 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 Delivery History and Stats

> Top-level Delivery History page, per-campaign funnel stats, what's durable vs what's cached, how read/opened/clicked are recorded.

# Delivery history and stats

Notifizz exposes two distinct surfaces for "what happened": **Delivery History** (what got sent) and **Statistics** (aggregated funnel per campaign). They share data sources but answer different questions.

## TL;DR

* **Delivery History** is a top-level page (`/org/:org/app/inbox`) — the timeline of every notification sent across all campaigns.
* **Stats** are funnel charts embedded in each campaign detail view — not a standalone page.
* Stats use a Redis-backed cache for fast aggregation; the durable source of truth is per-message delivery writes.
* Read / opened / clicked are recorded by the widget (NC) or the inbound webhook (email); each is a distinct funnel step.

## Delivery History

The top-level view answers "what was sent in the last hour / day / week?". It lists every workflow instance and every per-recipient delivery, with filters for campaign, recipient, channel, status, and time range.

### What you can do here

* **Trace a specific delivery** by message id or correlation id.
* **Filter by user** to see "did `u_42` receive the welcome notification?".
* **See the raw event payload** that triggered each delivery — useful for "did the orchestrator see what I expected?".
* **Inspect the rendered template** — the snapshot stored at message creation time, with variables resolved.

### Retention

Delivery records are retained per the org's plan (typically 90 days for the standard plan, with longer retention available). Aged-out records still keep their aggregate counters in stats — you lose the per-message detail but the campaign-level funnel survives.

## Statistics (per campaign)

The campaign detail page shows a funnel:

```
Delivery → Read → Opened → Clicked
```

Each step is a count, with a percentage of the previous step. The funnel applies per channel (NC, email) and per time window (24h, 7d, 30d, all-time).

### What each step means

| Step         | Notification Center                                       | Email                                                                   |
| ------------ | --------------------------------------------------------- | ----------------------------------------------------------------------- |
| **Delivery** | inbox write succeeded (message in user's inbox).          | Provider accepted (Postmark, …).                                        |
| **Read**     | User opened the dropdown and saw the message in the list. | Email client's "displayed in preview pane" signal — provider-dependent. |
| **Opened**   | User clicked into the message detail.                     | Tracking pixel fetched.                                                 |
| **Clicked**  | User clicked a link inside the message.                   | Click-tracked link followed.                                            |

Read vs Opened is the distinction most users miss — *Read* means the message was rendered in the list (passive), *Opened* means the user actively engaged with that specific message.

### Cache and durability

Funnel counts are computed from per-message events. To keep the dashboard responsive at scale, Notifizz caches aggregations in Redis.

| Layer                                | Durable?                              | Latency                        |
| ------------------------------------ | ------------------------------------- | ------------------------------ |
| Redis (cache)                        | no — recoverable from durable storage | \< 100 ms for typical queries  |
| durable storage (per-message events) | yes — source of truth                 | seconds for large aggregations |

The cache is invalidated on every write; you'll see new data within seconds of a delivery. The full durability analysis is in `backend/docs/stats-durability-analysis.md`.

## How events are recorded

| Event                               | Reported by                                        | Endpoint                               |
| ----------------------------------- | -------------------------------------------------- | -------------------------------------- |
| **send**                            | Backend, after inbox write succeeds                | internal queue event                   |
| **read** (NC)                       | Widget when the dropdown renders the message       | `POST /v1/notification/event/register` |
| **opened** (NC)                     | Widget when the user clicks into a message         | `POST /v1/notification/event/register` |
| **clicked** (NC + email)            | The widget (NC) or the link-route redirect (email) | `POST /v1/notification/event/register` |
| **read / opened / clicked** (email) | Postmark inbound webhooks                          | `POST /v1/webhooks/postmark/...`       |

Each event is keyed by `(messageId, eventType)` — duplicates are deduped (a user clicking twice doesn't double-count).

## Performance notes

* **Aggregation queries** hit Redis first. A cold cache for an unusual time window pays a backend round-trip; subsequent loads are fast.
* **Per-message events** are written directly to the inbox store — no in-memory queue. Each event has its own document so reads scale with the time window, not the lifetime of the campaign.
* **Real-time updates** on the widget side use real-time stream. Stats updates on the dashboard side use polling — refresh the page to recompute.

## FAQ

<AccordionGroup>
  <Accordion title="Open rate is below delivery rate — is that wrong?">
    No — it's the design. Delivery means the message landed in the user's inbox. Read means they saw it in the list (passive). Opened means they actively clicked into it. Each step strictly subset-reduces the previous, so percentages decrease down the funnel. A 100% open rate would be suspicious.
  </Accordion>

  <Accordion title="Export delivery history?">
    Yes — the Delivery History view has an export button (CSV). For programmatic access, the public read API exposes deliveries by campaign / time window. Both honor the same per-org retention.
  </Accordion>

  <Accordion title="Data retention — what's the default?">
    Per-message detail: 90 days on the standard plan. Aggregate counters in stats: indefinite (the count of messages sent in 2024 doesn't expire just because the messages themselves do). Adjust per-org via your plan settings.
  </Accordion>

  <Accordion title="Stats are stale — when does the cache refresh?">
    On every write that affects the aggregation. If you see stale numbers, the cause is usually time zone — check the time window selector in the funnel. The "all-time" window is always live.
  </Accordion>

  <Accordion title="Find one specific delivery for a user?">
    Two paths: (1) Delivery History → filter by `userId`; (2) the per-user inbox via the dashboard's user search. Both surface the same data; the user-search path is faster when you know the user.
  </Accordion>

  <Accordion title="What about A/B test branches in the funnel?">
    Branches in the orchestrator translate to separate workflow instances per campaign-version pair. The funnel slices by branch when the campaign declares branches explicitly; otherwise the aggregate sums them.
  </Accordion>
</AccordionGroup>

## See also

<CardGroup cols={2}>
  <Card title="Campaigns" icon="diagram-project" href="/docs/concepts/campaigns">
    Statuses, lifecycle, versioning.
  </Card>

  <Card title="Observability" icon="chart-line" href="/docs/operations/observability">
    Correlation IDs, tracing one delivery end-to-end.
  </Card>

  <Card title="Activity log" icon="clock-rotate-left" href="/docs/concepts/activity-log">
    Audit trail of campaign edits.
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/docs/operations/troubleshooting">
    "My notification didn't arrive" — checklist.
  </Card>
</CardGroup>
