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:

EventWhat Jitsu does
identifyCreates the contact if new, updates it otherwise, and reconciles its audience membership.
track / page / screenUpdates the existing contact's custom properties (never creates a contact).
groupUpdates 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 to context.traits.email). Events without an email are skipped — Resend requires an email.
  • First / last name come from traits.firstName / traits.lastName, or are split from traits.name.
  • unsubscribed is set from traits.unsubscribed when 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 userId and anonymousId are stored as jitsu_user_id and jitsu_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 resendAudiences trait is present, or the destination has configured Audiences. An event with neither leaves membership untouched.
  • Setting resendAudiences to an empty string explicitly clears the connector-managed audiences.
  • Only identify() manages membership; track / page / group events 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 userId resolves, the event is skipped (no contact is created or updated).

Configuration