Skip to main content

Recipients

Recipients are the audience a campaign delivers to. They’re produced server-side by the orchestrator, never picked at the SDK call site. This page covers the two patterns for producing them, the validation rules, and the gotchas.

TL;DR

  • Each recipient is at minimum { id: string, email: string }. Extra fields flow into the channel template.
  • Two production patterns inside a campaign: from the event’s own properties, or from an enricher. A one-off mailing to a CRM segment is a broadcast, a different kind of campaign.
  • id must be stable over time — it’s how the sends of one sequence are tied to the same person. An id that changes mid-sequence drops that person from it.
  • Empty recipient lists short-circuit — no message is created and no metric fires.
  • Invalid recipients (missing email or bad format) are skipped with a logged warning, not failed.

The shape

The orchestrator returns Recipient[]. For each recipient, the channel renders the template and delivers it. Recipient validation runs server-side at message creation. Invalid recipients are skipped — see “Validation rules” below.
For the Notification Center channel, recipient.id must exactly equal the identity your widget authenticates with (the userId it mounts). A mismatch delivers the notification but it never appears in the user’s inbox. Declare the field your widget uses under Settings → Notification Center → Authentification.

How recipients are recognised

Notifizz does not keep your user base: your identifier is the source of truth, at every send. A multi-message sequence follows each recipient by the id your application returns — the identifier is what makes “the same person” the same person between two sends. Consequence: if that identifier changes for someone mid-sequence, they’re no longer recognised as the same recipient and don’t receive the follow-ups. Use an identifier that is stable over time — your users’ internal id rather than any value that can change. The anti-pattern to avoid is falling back between identity fields:
The day u.id gets set — an account creation, a CRM sync — the recipient’s identifier flips from their email to their business id, and the sequence loses them. It’s the heterogeneity that breaks, not the absence: an audience keyed entirely on emails is perfectly stable. If your recipients have no internal identifier and are known by their email, Notifizz follows them on that email: creating their account mid-sequence doesn’t drop them out.

Where recipients come from

1. From event properties (direct)

The simplest case — the event already carries the recipient identifier.
When to use: when your code already knows the user id and email at the moment of track(). Most “single-recipient” campaigns fit this pattern.

2. Via an enricher (live lookup)

Pass a stable id, fetch the rest at notification time.
When to use:
  • The event payload should stay narrow (just identifiers), with detail fetched live.
  • Multiple campaigns need different fields from the same user — define one enricher, reuse it.
  • Email or other PII shouldn’t be in the event payload (compliance, privacy-friendly by default).
See enrichers tutorial for the wire-up.

Audiences that don’t come from the event

Some mailings aren’t triggered by a user action at all — a product announcement, a newsletter, a migration notice. That’s a broadcast: the audience is a CRM segment, read live at launch. The orchestrator still runs, but its job there is to map each record onto the template’s variables, not to find the audience. Inside an event-driven campaign, an audience wider than the event’s subject — every admin of a team, every watcher of a document — is still pattern 2: an enricher that returns a list instead of a single record.

Combining both

A campaign can use both patterns at once. A common one:
The role custom field lets the channel template branch — different copy per role, same campaign.

Validation rules

At message creation, every recipient is checked: Recipients that fail are skipped with a logged warning — the campaign doesn’t fail, but you don’t get a message for that recipient. Inspect the workflow trace and the application logs to identify skipped recipients. If the recipient list is empty, the system also short-circuits with a warning — no message is created, no send metric fires.

Custom fields

Anything beyond id + email is custom. Common patterns: The channel template has access to the full recipient object as {{ recipient }} (or your variable system’s equivalent). See brands and variables for variable resolution rules.

Audience size

Recipients are processed sequentially per workflow instance — the orchestrator returns the full list, then each recipient is delivered in turn. There’s no hard cap, but practical limits apply:
  • Per-workflow-instance — sub-second per recipient on healthy infrastructure. A 10,000-recipient campaign takes around 10 seconds end-to-end.
  • Total throughput — bound by the queue’s parallelism. Burst-fire scenarios benefit from transactional priority for the urgent campaigns.
  • Memory — the orchestrator’s return value is held in memory. Avoid > 100k recipients per single instance; paginate the enricher that produces the audience if you genuinely need that scale.
For broadcasts to large audiences, consider firing one event per recipient (more workflow instances, more parallelism) rather than one event with a giant recipient list.

Suppression and opt-out

Opt-out is a platform feature — you don’t re-implement it in the orchestrator. It is enforced after the orchestrator returns its list and before fan-out, recipient by recipient. On email, two layers apply, in this order: The suppression check runs first and always overrides a preference. It is fail-closed: if the list can’t be read, the recipient is dropped rather than emailed. Emailing a suppressed address because of a transient glitch is the one failure that can’t be taken back. The same promotional preferences apply to web push. Notification Center consent is applied when the inbox is read, not at send time. Nothing is swallowed: recipients dropped this way are counted in the campaign’s stats, under Address suppressed (bounce, spam or deletion) and Unsubscribed, next to the other drop reasons. What gets stored to make this work — hashes only, and for how long — is covered in privacy friendly.

Your own business rules

The platform layer answers “this person must never be emailed again”. A business rule — “skip accounts on the free plan”, “skip anyone who already received this offer this month” — is campaign logic: expose the flag on the enricher that already fetches the recipient, then prompt the AI to gate the send on it. It complements the suppression list; it never replaces it.

FAQ

Put userId in the event properties and let the orchestrator return that user as the only recipient. The SDK never picks recipients — that’s the deliberate design.
A mailing to a segment is a broadcast — the segment lives in your CRM and is read live at launch. Inside an event-driven campaign, a wider audience comes from an enricher that returns a list.
Three causes: (1) the enricher returned nothing; (2) the campaign’s conditions excluded everyone; (3) every resolved recipient was dropped downstream — suppression, unsubscribe, or failed validation. The workflow trace shows the orchestrator’s output, and the campaign’s stats break the drops down by reason.
Skipped with a logged warning. The campaign doesn’t fail; the other (valid) recipients still receive the message. Inspect the warning to identify the broken recipient and fix upstream (data, enricher, or orchestrator logic).
No platform-imposed cap, but practical limits apply (see “Audience size” above). For very large audiences, split via pagination or fire one event per recipient. For “soft caps” (e.g. “max 1000 recipients per campaign per day”), implement at the orchestrator level — fetch the count, refuse if over.
Yes. The suppression list is a platform feature: per organisation, holding email hashes only, fed automatically by hard bounces, spam complaints and erasure requests, and applied before fan-out — fail-closed, and ahead of any subscription preference. See Suppression and opt-out above. A business exclusion (“not to free-plan accounts”) is a different thing, and that one does belong in the campaign logic.

See also

Orchestrator

Where the recipient logic lives.

Enrichers tutorial

Live data lookups for recipient details.

Events

What gets passed in event.properties.

Brands and variables

Where recipient fields are referenced from templates.