Sequences
Sequences are automated multi-step email flows with branching conditions. Define emails, delays, and condition branches, then enroll contacts to start the drip.
How sequences work
Contact enrolled
│
├─ Step 0: Send "Welcome" email (immediately)
│
├─ Step 1: Wait 2 days (delay)
│
├─ Step 2: Send "Getting Started" email (after delay)
│
├─ Step 3: Wait 5 days (delay)
│
├─ Step 4: Condition — opened step 2?
│ ├─ TRUE → Step 5
│ └─ FALSE → Step 6
│
├─ Step 5: Send "Pro Tips" email (engaged path)
│ └─ Enrollment complete
│
└─ Step 6: Send "Re-engagement" email (unengaged path)
└─ Enrollment completeA cron job runs every minute to check for enrollments that need processing. If a contact unsubscribes, bounces, or is suppressed, the sequence stops automatically.
Cross-sequence send frequency cap
When a contact is enrolled in multiple sequences, you may want to avoid sending too many emails in a short period. Set a send frequency cap on the site to enforce a minimum number of days between sequence emails to the same contact, across all sequences on that site.
curl -X PUT https://pushmail.dev/api/v1/sites/1 \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"sendFrequencyCapDays": 3}'When the cap is hit, the enrollment is deferred (not skipped) — the cron worker re-checks every day until the cap window has passed, then sends the email and advances to the next step. Set to null to disable.
Note: The frequency cap only applies to sequence emails. Transactional sends (
POST /v1/send) and campaigns are not affected.
To see which contacts are currently enrolled in multiple sequences for a site, use the multi-sequence report:
curl "https://pushmail.dev/api/v1/reports/multi-sequence?siteId=1" \
-H "Authorization: Bearer pm_live_YOUR_KEY"This is also available in the dashboard under Reports.
Create a sequence
POST /v1/sequences
curl -X POST https://pushmail.dev/api/v1/sequences \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"siteId": 1,
"name": "Onboarding Drip",
"triggerType": "manual",
"sendingConfigId": 2
}'{
"data": {
"id": 1,
"siteId": 1,
"name": "Onboarding Drip",
"status": "draft",
"triggerType": "manual",
"createdAt": "2025-01-15T10:00:00.000Z"
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
siteId | integer | Yes | The site to create the sequence in |
name | string | Yes | Sequence name |
triggerType | string | Yes | "manual", "list_add", "tag_add", or "segment_enter" |
sendingConfigId | integer | Required to activate | Sending config to use for all emails in this sequence. Can be set at creation or before activation. See Sending for setup. |
Trigger types
| Type | Description |
|---|---|
manual | Enroll contacts via API or dashboard |
list_add | Auto-enroll when a contact is added to a specific list |
tag_add | Auto-enroll when a contact receives a specific tag |
segment_enter | Auto-enroll when a contact enters a dynamic segment |
For auto-enrollment triggers, set triggerConfig as a JSON string on the sequence:
list_add: settriggerConfig: "{\"listId\":5}"to auto-enroll contacts when they are added to list 5tag_add: settriggerConfig: "{\"tagId\":3}"to auto-enroll contacts when tag 3 is appliedsegment_enter: settriggerConfig: "{\"segmentId\":1}"to auto-enroll contacts when they match segment 1
You can also set an optional goalConfig (JSON string) for conversion tracking (e.g. to end the sequence early when a contact reaches a goal).
Add steps
POST /v1/sequences/:seqId/steps
Steps execute in order by position. Three step types: email, delay, and condition.
curl -X POST https://pushmail.dev/api/v1/sequences/1/steps \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"steps": [
{ "position": 0, "type": "email", "templateId": 5 },
{ "position": 1, "type": "delay", "delayMinutes": 4320 },
{
"position": 2,
"type": "condition",
"conditionConfig": "{\"type\":\"engagement_check\",\"metric\":\"opened\",\"stepOrder\":0,\"operator\":\"did\"}",
"trueBranchStepOrder": 3,
"falseBranchStepOrder": 4
},
{ "position": 3, "type": "email", "templateId": 6 },
{ "position": 4, "type": "email", "templateId": 7 }
]
}'Step fields
| Field | Type | Required | Description |
|---|---|---|---|
position | integer | Yes | Step order (0-based) |
type | string | Yes | "email", "delay", or "condition" |
templateId | integer | Email steps | Template to send |
delayMinutes | integer | Delay steps | Minutes to wait |
conditionConfig | string (JSON) | Condition steps | Condition rule to evaluate (see below) |
trueBranchStepOrder | integer or null | Condition steps | Position to jump to if condition is true. Default: next step |
falseBranchStepOrder | integer or null | Condition steps | Position to jump to if condition is false. Default: complete sequence |
Batch limit: A maximum of 200 steps can be created per request. If you need more steps, split them across multiple API calls.
Common delay values: 60 (1 hour), 1440 (1 day), 4320 (3 days), 10080 (1 week), 43200 (30 days)
Condition steps
Condition steps evaluate a rule against the enrolled contact's data and route the enrollment down different paths. The conditionConfig field is a JSON string with the condition rule.
Supported condition types
Tag check
Check if a contact has or does not have a specific tag.
{
"type": "tag_check",
"tagId": 5,
"operator": "has"
}| Field | Values | Description |
|---|---|---|
tagId | integer | The tag ID to check |
operator | "has" or "not_has" | Whether the contact should have the tag |
Field check
Check a contact's field value (top-level fields or custom fields).
{
"type": "field_check",
"field": "customFields.plan_type",
"operator": "equals",
"value": "pro"
}| Field | Values | Description |
|---|---|---|
field | string | Field path. Use customFields.key for custom fields, or a top-level field name like firstName, company, etc. |
operator | "equals", "not_equals", "contains", "exists" | Comparison operator |
value | string (optional) | Value to compare against. Not needed for "exists" |
Engagement check
Check if a contact opened or clicked a specific previous step's email.
{
"type": "engagement_check",
"metric": "opened",
"stepOrder": 0,
"operator": "did"
}| Field | Values | Description |
|---|---|---|
metric | "opened" or "clicked" | What engagement to check |
stepOrder | integer | Position of the email step to check |
operator | "did" or "did_not" | Whether the contact should have engaged |
Event check
Check if a contact has a specific event type in their send history.
{
"type": "event_check",
"eventName": "purchased",
"operator": "performed",
"withinDays": 7
}| Field | Values | Description |
|---|---|---|
eventName | string | The event type to look for (e.g. "delivered", "open", "click") |
operator | "performed" or "not_performed" | Whether the event should have occurred |
withinDays | integer (optional) | Only check events within this many days |
Branching behavior
When a condition step is evaluated:
- If TRUE and
trueBranchStepOrderis set, the enrollment jumps to that step position - If TRUE and
trueBranchStepOrderis null, the enrollment advances to the next sequential step - If FALSE and
falseBranchStepOrderis set, the enrollment jumps to that step position - If FALSE and
falseBranchStepOrderis null, the enrollment completes (sequence ends)
The condition evaluation is logged in the enrollment step logs with a status of "condition_true" or "condition_false".
Activate a sequence
Sequences start in draft status. A sendingConfigId is required before activation — this tells PushMail which from address and provider to use for all emails in the sequence.
curl -X PUT https://pushmail.dev/api/v1/sequences/1 \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "active"}'curl -X PUT https://pushmail.dev/api/v1/sequences/1 \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "active", "sendingConfigId": 2}'You can pause a sequence at any time. Active enrollments will stop processing until you reactivate.
curl -X PUT https://pushmail.dev/api/v1/sequences/1 \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "paused"}'Auto BCC
You can set an autoBcc address on a sequence to automatically BCC every email sent through it. This is useful for CRM integration, compliance archiving, or internal monitoring.
curl -X PUT https://pushmail.dev/api/v1/sequences/1 \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"autoBcc": "archive@yourcompany.com"}'If no sequence-level autoBcc is set, the site-level autoBcc is used (if configured via PUT /sites/:id). Set autoBcc to null to clear it.
Enroll a contact
POST /v1/sequences/:id/enroll
Enrolling contacts starts them at step 0 by default. Each contact can only be enrolled once per sequence (duplicates are rejected). You can enroll 1 to 100 contacts per request.
Use startAtStep to begin at a specific step position — useful for migrating subscribers who are already mid-drip from another system.
| Parameter | Type | Description |
|---|---|---|
contactIdsrequired | number[] | Array of contact IDs to enroll (1–100) |
startAtStep | number | Step position to start at (0-indexed). Defaults to 0 (first step). |
curl -X POST https://pushmail.dev/api/v1/sequences/1/enroll \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "contactIds": [42] }'curl -X POST https://pushmail.dev/api/v1/sequences/1/enroll \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "contactIds": [42], "startAtStep": 3 }'{
"data": {
"enrolled": 1
}
}Full example: Welcome series
Here's how to set up a 3-email welcome sequence and enroll new signups automatically from your app:
API="https://pushmail.dev/api/v1"
KEY="pm_live_YOUR_KEY"
# 1. Create the sequence
SEQ=$(curl -s -X POST "$API/sequences" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"siteId": 1, "name": "Welcome Series", "triggerType": "manual"}' \
| jq -r '.data.id')
# 2. Add steps: email → delay → email → delay → email
curl -X POST "$API/sequences/$SEQ/steps" -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{
"steps": [
{"position": 1, "type": "email", "templateId": 1},
{"position": 2, "type": "delay", "delayMinutes": 2880},
{"position": 3, "type": "email", "templateId": 2},
{"position": 4, "type": "delay", "delayMinutes": 7200},
{"position": 5, "type": "email", "templateId": 3}
]
}'
# 3. Activate
curl -X PUT "$API/sequences/$SEQ" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"status": "active"}'
echo "Sequence $SEQ is live!"// After creating the user in your database...
const contact = await fetch("https://pushmail.dev/api/v1/contacts", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.PUSHMAIL_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
siteId: 1,
email: user.email,
firstName: user.name,
tags: ["signup"],
}),
}).then(r => r.json());
// Enroll in welcome sequence
await fetch(`https://pushmail.dev/api/v1/sequences/${WELCOME_SEQ_ID}/enroll`, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.PUSHMAIL_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ contactIds: [contact.data.id] }),
});Sequence Playbooks
Playbooks are pre-built sequence templates that you can deploy with a single API call. Instead of manually creating a sequence, adding templates, and wiring up steps, just pick a playbook and go.
List available playbooks
GET /v1/sequences/playbooks
No authentication required. Returns all available playbook templates with metadata.
curl https://pushmail.dev/api/v1/sequences/playbooks{
"data": {
"playbooks": [
{
"id": "welcome-series",
"name": "Welcome Series",
"description": "Introduce your brand and set expectations for new subscribers...",
"category": "onboarding",
"recommendedTrigger": "list_add",
"suggestedTriggerValue": "new-subscribers",
"stepCount": 3,
"steps": [
{ "type": "email", "emailSubject": "Welcome to {{company}}!" },
{ "type": "delay", "delayMinutes": 2880 },
{ "type": "email", "emailSubject": "Getting started with {{company}}" }
]
}
]
}
}Available playbooks
| ID | Name | Category | Steps | Description |
|---|---|---|---|---|
welcome-series | Welcome Series | onboarding | 3 | Brand intro + feature highlights after 2 days |
onboarding-drip | Onboarding Drip | onboarding | 5 | Quick start + two feature spotlights over several days |
re-engagement | Re-engagement / Win-back | retention | 3 | Incentive-driven win-back with final reminder |
post-purchase | Post-Purchase Follow-up | sales | 4 | Thank you, usage tips, and review request over 10 days |
newsletter-nurture | Newsletter Nurture | engagement | 3 | Subscription confirmation + best content roundup |
product-launch | Product Launch | sales | 4 | Teaser, launch announcement, and social proof |
Create a sequence from a playbook
POST /v1/sequences/playbooks/:playbookId/create
Auth required. Creates a complete sequence with templates and steps from the chosen playbook.
curl -X POST https://pushmail.dev/api/v1/sequences/playbooks/welcome-series/create \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"siteId": 1,
"name": "My Welcome Series"
}'{
"data": {
"sequence": {
"id": 5,
"siteId": 1,
"name": "My Welcome Series",
"status": "draft",
"triggerType": "list_add",
"triggerConfig": "{\"value\":\"new-subscribers\"}"
},
"steps": [
{ "id": 10, "position": 1, "type": "email", "templateId": 22, "templateName": "My Welcome Series — Welcome to {{company}}!" },
{ "id": 11, "position": 2, "type": "delay", "delayMinutes": 2880 },
{ "id": 12, "position": 3, "type": "email", "templateId": 23, "templateName": "My Welcome Series — Getting started with {{company}}" }
],
"playbook": {
"id": "welcome-series",
"name": "Welcome Series"
}
}
}| Parameter | Type | Description |
|---|---|---|
siteIdrequired | number | Site to create the sequence in |
name | string | Custom name (defaults to the playbook name) |
sendingConfigId | number | Sending config to use for the sequence |
The sequence is created in draft status. Customize the generated templates, then activate the sequence when ready. All email templates use {{first_name}}, {{company}}, and {{unsubscribe_url}} variables that get replaced at send time.
Quick start: playbook to live sequence in 2 API calls
API="https://pushmail.dev/api/v1"
KEY="pm_live_YOUR_KEY"
# 1. Create from playbook (creates sequence + templates + steps)
SEQ=$(curl -s -X POST "$API/sequences/playbooks/welcome-series/create" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"siteId": 1, "name": "Welcome Series"}' \
| jq -r '.data.sequence.id')
# 2. Activate
curl -X PUT "$API/sequences/$SEQ" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"status": "active"}'
echo "Sequence $SEQ is live!"Holiday email injection
Inject holiday-specific emails into active sequences without disrupting anyone's position. On the scheduled date, every active enrollment receives the holiday email before their next normal step, then resumes exactly where they left off.
This works regardless of where each contact is in the sequence — some may be on step 2, others on step 8. Everyone gets the holiday email, then continues their normal flow.
Schedule a holiday
POST /v1/holidays
| Parameter | Type | Description |
|---|---|---|
siteIdrequired | number | Site to schedule the holiday for |
namerequired | string | Holiday name (e.g. "Black Friday 2026") |
templateIdrequired | number | Template to send as the holiday email |
daterequired | string | Date to send (YYYY-MM-DD format) |
sequenceIds | number[] | Limit to specific sequence IDs. Omit to apply to all active sequences on the site. |
sendingConfigId | number | Override sending config. Falls back to each sequence's own config. |
curl -X POST https://pushmail.dev/api/v1/holidays \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"siteId": 1,
"name": "Black Friday 2026",
"templateId": 15,
"date": "2026-11-27"
}'{
"data": {
"id": 1,
"siteId": 1,
"name": "Black Friday 2026",
"templateId": 15,
"date": "2026-11-27",
"status": "scheduled",
"sentCount": 0
}
}Scope to specific sequences
By default, a holiday email applies to all active sequences on the site. To limit it to specific sequences:
curl -X POST https://pushmail.dev/api/v1/holidays \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"siteId": 1,
"name": "Christmas Sale",
"templateId": 20,
"date": "2026-12-25",
"sequenceIds": [1, 3]
}'How it works
- On the scheduled date, the cron job checks each active enrollment before processing its normal step
- If a holiday email is due and hasn't been sent to that enrollment yet, it sends the holiday email
- Normal step processing is deferred by 1 hour to avoid double-sending
- On the next cron tick, the enrollment resumes its normal sequence from where it left off
- After the date passes, the holiday is automatically marked as
completed
Holiday statuses
| Status | Description |
|---|---|
scheduled | Created but the date hasn't arrived yet |
active | Currently sending to enrollments (date is today) |
completed | Date has passed, all sends finished |
cancelled | Manually cancelled before completion |
List holidays
GET /v1/holidays?siteId=1
curl "https://pushmail.dev/api/v1/holidays?siteId=1" \
-H "Authorization: Bearer pm_live_YOUR_KEY"Cancel a holiday
PUT /v1/holidays/:id
curl -X PUT https://pushmail.dev/api/v1/holidays/1 \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "cancelled"}'Delete a holiday
DELETE /v1/holidays/:id
curl -X DELETE https://pushmail.dev/api/v1/holidays/1 \
-H "Authorization: Bearer pm_live_YOUR_KEY"Cancel enrollments
DELETE /v1/sequences/:id/enrollments
Cancel one or more active enrollments. Supports three modes: single (query param), bulk (JSON body with IDs), and by contact ID.
curl -X DELETE "https://pushmail.dev/api/v1/sequences/1/enrollments?enrollment_id=42" \
-H "Authorization: Bearer pm_live_YOUR_KEY"curl -X DELETE https://pushmail.dev/api/v1/sequences/1/enrollments \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"enrollmentIds": [42, 43, 44]}'{
"data": {
"cancelled": 3,
"total": 3
}
}curl -X DELETE https://pushmail.dev/api/v1/sequences/1/enrollments \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"contactId": 19}'{
"data": {
"cancelled": true,
"contactId": 19
}
}Enrollment statuses
| Status | Description |
|---|---|
active | Currently progressing through steps |
completed | All steps processed |
cancelled | Manually cancelled or contact unsubscribed |
failed | Processing error (contact bounced, etc.) |
Next steps
- Segments -- Use dynamic audiences to trigger sequence enrollment
- Campaigns -- Send one-off broadcasts to a list, tag, or segment
- Webhooks -- Track delivery, opens, and clicks in real-time
- API Reference -- Complete endpoint documentation