Resend
Resend is an email API for developers. This destination syncs your Jitsu users to Resend as contacts, so you can email or broadcast to them and manage their segment membership.
How it works
Resend identifies contacts by email address — a contact is a global entity keyed by its email. Resend has
no concept of a userId, so email is the only stable key. Jitsu maps events to Resend contacts as follows:
| Event | What Jitsu does |
|---|---|
identify | Creates the contact if new, updates it otherwise, and reconciles its audience membership. |
track / page / screen | Updates the existing contact's custom properties (never creates a contact). |
group | Updates the existing contact with the group id and group traits as group_* properties. |
identify
Each identify() upserts a contact:
- Email is read from
traits.email(falling back tocontext.traits.email). Events without an email are skipped — Resend requires an email. - First / last name come from
traits.firstName/traits.lastName, or are split fromtraits.name. unsubscribedis set fromtraits.unsubscribedwhen it is a boolean.- All other traits are stored as Resend contact properties
(custom fields). Resend properties are strings, so non-string values are coerced (objects are JSON-encoded).
The Jitsu
userIdandanonymousIdare stored asjitsu_user_idandjitsu_anonymous_id. Resend requires every property to be defined before it can be set, so Jitsu automatically creates a string-typed property definition for each new trait key the first time it sees it. - The contact's audience membership is reconciled to match the configured set.
A repeated identify() for the same email updates the existing contact rather than creating a duplicate.
Other events
track, page and screen events update an existing contact but never create one. An update happens only
when the event carries custom contact fields (typically added by a
transformation function) — a plain event with just an email does nothing. The email is
resolved from the event's traits, or from the userId cache (see below). If no
contact matches, the event is skipped.
group events fold the groupId and group traits into the contact's properties, namespaced as group_<key>.
They don't change audience membership.
Audiences
Set Audiences in the destination config to a comma-separated list of audience names (not IDs) — for
example Customers, Beta. Jitsu resolves each name to a Resend audience, creating any that don't exist yet.
Leave it empty to add contacts without an audience.
You can override the audiences per event by setting a resendAudiences trait (also comma-separated names) from
a function:
export default async function (event) {
if (event.traits?.plan === "enterprise") {
event.traits.resendAudiences = "Customers, Enterprise";
}
return event;
}Membership is reconciled
On each identify(), Jitsu makes the contact's membership match the desired set. Audiences it previously
added that are no longer listed are removed. For example, if a function returns A, B on one event and
B, C on the next, the contact is removed from A, kept in B, and added to C.
To do this safely, the connector records which audiences it added in a contact property named
jitsu_managed_audiences — so it only ever removes audiences it added, never ones you added manually or via
another tool. There's no separate state stored in Jitsu; the record lives on the contact.
Notes:
- Reconciliation runs only when a desired set is provided for the event — i.e. the
resendAudiencestrait is present, or the destination has configured Audiences. An event with neither leaves membership untouched. - Setting
resendAudiencesto an empty string explicitly clears the connector-managed audiences. - Only
identify()manages membership;track/page/groupevents never change it.
Matching events by userId
Resend cannot look a contact up by userId — only by email or Resend's own contact id. So by default, only
events that carry an email are matched to a contact.
Enable Resolve email from userId to change this. When on, every identify() caches a userId → email
mapping, and later track / page / group events that carry only a userId are matched to the contact via
that cache. Notes:
- The cache is only populated from
identify()calls seen after the flag is enabled — historical mappings are not backfilled. - If neither an email nor a cached
userIdresolves, the event is skipped (no contact is created or updated).