Sequences
Automated email sequences (Resend automations) triggered by events, list signups, new contacts or segments, with delays, waits, condition and random splits, contact updates and per-step stats.
A sequence is an automated series of steps for each contact who enters it: onboarding, trial nurture, re-engagement. Resend calls them automations; /automations is an alias of /sequences (see Resend aliases).
Triggers
trigger.type | Enrolls a contact when |
|---|---|
event | Your app records the custom event event for it |
list_joined | It becomes a subscribed member of list_id (after double opt-in, if the list has it) |
contact_created | It is created, by the API, an import or a form (a double opt-in form signup enters when it confirms) |
segment_entered | It starts matching segment_id (checked every few minutes; contacts already matching when the sequence is activated are not enrolled) |
A contact is enrolled once per sequence unless allow_reentry is true. POST /sequences/:id/enrollments enrolls up to 1,000 contacts by hand, ignoring the trigger but honoring re-entry. Unsubscribed contacts are never enrolled.
Steps
steps is a graph: an entry step id and a map of steps, each naming the step that follows (null ends the sequence).
{
"name": "Trial onboarding",
"trigger": { "type": "event", "event": "trial.started" },
"steps": {
"entry": "welcome",
"steps": {
"welcome": { "type": "send_email", "template_id": "tpl_…", "next": "wait" },
"wait": {
"type": "wait_for_event", "event": "project.created",
"timeout": { "amount": 3, "unit": "days" },
"next": "tips", "timeout_next": "nudge"
},
"nudge": { "type": "send_email", "template_id": "tpl_…", "next": null },
"tips": { "type": "delay", "duration": { "amount": 2, "unit": "days" }, "next": "is_pro" },
"is_pro": {
"type": "condition",
"filter": { "type": "property", "key": "plan", "operator": "eq", "value": "pro" },
"if_true": null, "if_false": "upgrade"
},
"upgrade": { "type": "send_email", "template_id": "tpl_…", "subject": "Ready for Pro?", "next": null }
}
}
}| Step | Does |
|---|---|
send_email | Sends a published template, optionally overriding from and subject |
delay | Waits a fixed duration, until_time (the next of some weekdays at a time in a time zone) or until_date (a date on the contact, with an offset) |
wait_for_event | Waits for event, then goes to next; after timeout, goes to timeout_next |
condition | Evaluates a segment filter for the contact and goes to if_true or if_false |
condition_split | Checks ordered paths, each with a segment filter; the contact takes the first that matches, else else_next |
random_split | Sends each contact down one of 2–5 paths at random, by whole-number weights that add up to 100 |
update_contact | Sets first_name, last_name or properties, inside a set object: {"type": "update_contact", "set": {"properties": {"plan": "pro"}}, "next": "..."} |
add_to_list, remove_from_list | Changes a list membership. add_to_list never overrides the contact: an opt-out or a pending double opt-in confirmation is kept, and a double opt-in list is skipped (only the contact's confirmation subscribes them) |
exit | Ends the enrollment |
Delays
A delay sets exactly one of:
{ "type": "delay", "duration": { "amount": 2, "unit": "days" }, "next": "…" }
{ "type": "delay", "until_time": { "weekdays": ["mon", "thu"], "time": "09:00", "timezone": "Europe/Berlin" }, "next": "…" }
{ "type": "delay", "until_date": { "property": "renews_at", "offset": { "amount": 3, "unit": "days", "direction": "before" } }, "next": "…" }
{ "type": "delay", "until_date": { "property": "renews_at", "time": "09:00", "timezone": "America/New_York" }, "next": "…" }until_time continues at the first listed weekday and time strictly after the contact arrives, in the given IANA time zone (daylight saving changes included). until_date waits for a custom date property, or created_at, moved by the optional offset. A date-only value (2026-12-25) is stored as midnight UTC, which is the evening before in the Americas: add time and timezone (both or neither) to continue at that wall-clock time on the contact's date in that zone, then apply the offset (with "direction": "before" and 1 day, the 24th at 09:00 New York time). A full timestamp is read on that zone's clock. If the contact has no valid date, or the moment has passed, the contact continues at once and the enrollment history records why (continued:property_missing, continued:property_invalid or continued:date_passed).
Splits
{
"type": "condition_split",
"paths": [
{ "name": "Pro", "filter": { "type": "property", "key": "plan", "operator": "eq", "value": "pro" }, "next": "pro_tips" },
{ "name": "Team", "filter": { "type": "property", "key": "plan", "operator": "eq", "value": "team" }, "next": "after" }
],
"else_next": "after"
}{
"type": "random_split",
"paths": [
{ "name": "A", "weight": 50, "next": "subject_a" },
{ "name": "B", "weight": 50, "next": "subject_b" }
]
}A path may continue straight to the step where the paths meet again (an empty path). A random split assigns each enrollment by a hash of the enrollment and step ids, so a retried or resumed enrollment always takes the same path. The two-way condition step keeps working.
The graph is checked when you save it: every referenced step, template, list and date property must exist, every step must be reachable, every loop must pass a timed wait (a delay for a duration or until a weekday and time, or a wait_for_event), random split weights must add up to 100, and every filter must compile. A sequence has up to 100 steps.
Properties of the triggering event become template variables of the emails it sends, so a trial.started event with { "trial_end": "Nov 3" } fills {{ trial_end }}.
Lifecycle
| Status | Meaning |
|---|---|
draft | Editable, not enrolling |
active | Enrolling on its trigger and running enrollments |
paused | Not enrolling; enrollments hold where they are until it is activated again |
archived | Final: every live enrollment is canceled |
POST /sequences/:id/activate needs the project's physical address when the sequence sends email. Changing steps creates a new graph version: contacts already enrolled finish on the version they started.
Each email is checked before it goes out. A contact who unsubscribed (or left the trigger list of a list_joined sequence: opted out, removed, or the list was deleted) exits the sequence; a suppressed address or an opt-out of a list is skipped and the sequence continues. A step that fails temporarily is retried with backoff from 5 minutes up to 6 hours (8 attempts, then the enrollment fails). A step blocked by something you fix outside the sequence (the monthly marketing send quota, an unverified sender domain, an unpublished template, a missing physical address or sender) waits on that step, checked again hourly, and continues once it is fixed: it never fails the enrollment. Sequence emails count as marketing emails and carry the same unsubscribe headers and footer as campaigns; their unsubscribe link opts the contact out of all marketing email.
Test and monitor
POST /sequences/:id/testis a dry run for one contact: conditions are evaluated against the real contact, emails are rendered, and nothing changes.assume_eventslists events a wait should treat as received. From a dashboard session,send_towith your own address also sends each email to you, with[Test]in the subject.GET /sequences/:id/enrollmentslists enrollments with theirstatus(active,waiting,completed,exited,canceled,failed), current step and step history. Cancel one withPOST /sequences/:id/enrollments/:enrollment_id/cancel.GET /sequences/:id/statsreturns per-step counters: entered, completed, sent, skipped, failed, exited, branch outcomes, events received and timeouts, emails opened and clicked (forsend_emailsteps), plus how many enrollments are on each step now.
Reference: Sequences.
Campaigns
Send one-off marketing campaigns (Resend broadcasts) to lists and segments through Amazon SES, schedule, pause, resume or cancel them, and track per-recipient delivery stats.
Events
Send custom contact events such as trial.started from your app to trigger sequences, resume waiting steps and filter segments, with idempotency keys.