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

# Notifications Without Warehouse Sync — Enrichers

> Customer.io, Braze, Iterable copy your customer data into their database. Notifizz doesn't. Enrichers fetch live data at notification time — GDPR-friendly, real-time, no ETL.

# Why enrichers

Customer.io, Braze, Iterable. Different visual canvases; same backbone. Each runs on a copy of your customer data — synced from your warehouse, your CRM, your application database, into the platform's own database. The sync is permanent. The drift is permanent. The privacy surface is permanent.

We think that's a structural mistake the entire category inherited from CRM-era tools. Notifizz doesn't sync.

## What we believe

Customer data should stay where it lives. The notification platform should fetch what it needs at the moment it sends, from the source of truth, with no copy. We call this primitive an **enricher** — a server-side function the orchestrator calls at notification time. It's the difference between a permanent ETL pipeline and a one-line API call.

## The status quo

Every major marketing automation platform is a warehouse-sync platform under the hood:

| Platform    | Where customer data lives | How it gets there                                        |
| ----------- | ------------------------- | -------------------------------------------------------- |
| Customer.io | Customer.io database      | Reverse ETL from your warehouse / API push from your app |
| Braze       | Braze database            | Reverse ETL from your warehouse / API push               |
| Iterable    | Iterable database         | Reverse ETL from your warehouse / catalogue uploads      |

The pattern is identical. Your data lives in N places (warehouse, CRM, application DB, Braze). The notification platform reads from its copy. To keep the copy fresh, an integration runs continuously — Hightouch, Census, your own ETL, the platform's import API.

Three problems compound.

### 1. Drift

Last night's sync ran. This morning, the user changed their email. The campaign fires. The platform sees the old email; the user gets the notification on an address they don't read anymore. Drift is structural, not configurable — it's the gap between when the sync ran and when the notification fired.

### 2. Privacy and data minimisation

GDPR Article 5 says you should hold customer data only for as long as you need it. Warehouse-sync platforms hold a copy permanently — for every customer who might receive a notification, regardless of whether one ever fires. The DPO has to audit a separate database, evaluate a separate vendor, and sign a separate DPA, all for a copy of data they already audit in your warehouse.

### 3. Cost and complexity

The sync pipeline is its own engineering surface. New customer attribute? Schema migration in the platform + ETL job update. Rename a field? Coordinated change across two systems. Deprecate a field? Hope nothing in the platform still reads it. Reverse ETL vendors exist as a category specifically because this is hard.

## The Notifizz alternative

Notifizz holds **no customer data**. Recipient identifiers (`userId`, `email`) ride on the event. Anything else the campaign needs comes from an **enricher** — a function you register in the Node SDK that fetches live data from your systems.

```javascript theme={null}
client.enricher("fetchUser", {
  input: z.object({ userId: z.string() }),
  output: z.object({ id: z.string(), email: z.string(), plan: z.string() }),
  cache: { ttl: "1h" },
  handler: async ({ userId }) => {
    const user = await db.users.findOne({ id: userId });
    return { id: user.id, email: user.email, plan: user.plan };
  },
});
```

The orchestrator calls `enrichWith("fetchUser", { userId })` at notification time. Notifizz POSTs your endpoint with HMAC-signed headers; your service returns the live record. The campaign uses what came back.

Three benefits — each the inverse of a status-quo problem.

### Real-time consistency

The user's email is whatever your DB says it is **at notification time**. There's no sync window, no nightly batch, no drift. Update propagation is the latency of one HTTP call (typically sub-100ms with a cache hit).

### GDPR / data minimisation

Notifizz processes the customer data, doesn't store it. The privacy surface is the event payload (which you control) and the per-message snapshot (delivery history). The customer data graph stays in your systems where your DPO already audits it. Adding Notifizz doesn't add a copy to evaluate.

### Simplicity

No sync pipeline. New attribute the campaign needs? Add a field to the enricher's output schema; deploy. Rename? Update one file. Deprecate? Delete the field. The notification platform consumes your live data the same way the rest of your application does.

The protocol — discovery, HMAC, anti-replay, cache policy — is documented in [enrichers protocol](/docs/sdks/event-tracking/enrichers-protocol). The end-to-end tutorial is in [enrichers tutorial](/docs/sdks/how-to/enrichers-tutorial).

## Caching is not sync

The objection writes itself: "but you cache enricher responses — isn't that a sync?"

No. The differences matter:

| Sync (warehouse-sync platforms)                    | Cache (Notifizz enrichers)                                         |
| -------------------------------------------------- | ------------------------------------------------------------------ |
| Pre-fetches data **before** any notification fires | Fetches data **only when a notification fires that needs it**      |
| Stores **every customer** in the platform's DB     | Stores **only the records used recently**, evicted by TTL          |
| Holds the data indefinitely                        | Evicts on TTL or on cache invalidation                             |
| Privacy surface = full customer database           | Privacy surface = the records actively used in the last TTL window |
| New attribute requires schema migration            | New attribute is a field in the enricher's response                |

A cache is an optimisation over the source of truth; a sync is a parallel source of truth. The data lifecycle is different, the privacy posture is different, the engineering surface is different.

## When sync would actually beat enrichers

Two cases where sync's a defensible choice:

1. **Massive batch campaigns where the cost of N enricher calls exceeds the cost of a sync.** A weekly newsletter to 5M subscribers may benefit from a pre-fetched audience. We're aware of the tradeoff; the platform's batch dispatcher can be tuned, and most teams that hit this scale have a hybrid pattern (sync the audience definition, enrich the per-recipient personalisation).

2. **Audiences computed from heavy joins your DB can't serve at notification-time speed.** If your "users at risk of churn" requires three minutes of warehouse SQL, you can't compute it on a hot path. The pattern: precompute the audience nightly into a saved [dynamic recipient source](/docs/concepts/recipients), trigger a campaign on it. The sync still happens — but for the audience definition, not the customer attributes.

For everything else — the 99% case of "send transactional + marketing notifications to customers based on their current state" — enrichers strictly dominate.

## But what about…

<AccordionGroup>
  <Accordion title="We sync to Snowflake / BigQuery already. Would enrichers be slower?">
    Enrichers run against your operational systems (application DB, internal API), not against the warehouse. The cache policy means a popular enricher hits your DB at most once per TTL window per (orgId, name, paramsHash). For the typical pattern — a `fetchUser` cached 1h — your DB sees a few requests per second per popular user, not one per notification. The latency budget is sub-second.
  </Accordion>

  <Accordion title="Latency overhead per notification?">
    Cold cache: one HTTP round-trip to your service plus your handler's work. Most teams see 50-200ms. Hot cache: zero — the orchestrator gets the cached value, your service isn't called. End-to-end (event acceptance → bell update) typically lands sub-second on healthy infrastructure.
  </Accordion>

  <Accordion title="Can enrichers handle 50k recipients in one campaign?">
    Yes. The orchestrator's recipient list iterates per recipient; the enricher's response can resolve recipient details (email, locale, …) per-recipient via cache. For large fan-outs, the dominant cost is per-message dispatch, not enricher calls — enrichers cache aggressively and reuse across recipients sharing the same lookup keys.
  </Accordion>

  <Accordion title="Our customer data lives in 5 systems. How do enrichers handle that?">
    Register one enricher per source. `fetchUser` from your DB, `fetchSubscription` from your billing system, `fetchSegmentMembership` from your audience tool. The orchestrator calls each as needed. The stitching happens server-side, in code you control — not in a vendor's data model.
  </Accordion>

  <Accordion title="How is this different from Customer.io's webhook trigger?">
    Customer.io's webhook *triggers* a campaign — it accepts an inbound event. Notifizz enrichers *enrich* a notification — they're outbound calls from the platform to your service, made at the moment the notification needs the data. Customer.io still runs on its synced copy of customer data; the webhook is just one way to start a journey on that data. Different layer of the architecture.
  </Accordion>

  <Accordion title="Caching — am I just rebuilding sync?">
    No. A cache holds **the records you've actually used recently**, evicted by TTL. A sync holds **every customer**, indefinitely, regardless of whether the platform ever uses them. The privacy surface, the cost, and the freshness story are all different. See "Caching is not sync" above.
  </Accordion>

  <Accordion title="What if my enricher endpoint is down?">
    Notifizz retries with backoff; persistent failures surface in the workflow trace as a typed error. The campaign can branch on enricher failures — proceed with degraded data, fail gracefully, retry later. The failure surface is explicit; you choose the policy.
  </Accordion>
</AccordionGroup>

## Where to go next

<CardGroup cols={2}>
  <Card title="Marketing autonomy" icon="users-rectangle" href="/docs/why/marketing-team-autonomy">
    The hero pillar — enrichers are one of four ingredients.
  </Card>

  <Card title="Enrichers tutorial" icon="puzzle-piece" href="/docs/sdks/how-to/enrichers-tutorial">
    Register and mount your first enricher in five minutes.
  </Card>

  <Card title="Enrichers protocol" icon="code" href="/docs/sdks/event-tracking/enrichers-protocol">
    Wire format, HMAC, cache policy, error semantics.
  </Card>

  <Card title="Recipients" icon="user" href="/docs/concepts/recipients">
    How enricher output composes with recipient resolution.
  </Card>
</CardGroup>
