An automation is a drip sequence: a contact is enrolled when a trigger fires — they were added to a list, tagged, or submitted a signup form — and a background scheduler then walks that enrollment through an ordered list of wait and email steps. Write the sequence once; every matching contact flows through it on their own clock. The endpoints live under /v1/automations.
Each email step runs through the same pipeline as POST /v1/send and broadcasts, so suppression, unsubscribe handling, spend caps, and idempotency all apply automatically — and every marketing email carries an unsubscribe footer. You build the journey; Sendara handles delivery and consent.
POST /v1/send; for one message to a whole list at once, use broadcasts.Draft, active, paused
An automation has three states. It is created as a draft and sends nothing until you explicitly activate it — the enroller and scheduler only ever act on active rows. This makes it safe to build and edit a sequence in full before a single contact is ever touched.
| Status | Meaning |
|---|---|
| draft | The default on create. Fully editable. No contacts enroll and nothing sends — a safe place to build the sequence. |
| active | Live. Matching contacts are enrolled as triggers fire, and the scheduler advances every enrollment through the steps. |
| paused | Frozen. No new enrollments, and in-flight enrollments stop advancing — they resume from where they parked when you reactivate. |
/activate, an automation is inert. A draft or paused automation enrolls no one and sends no email — this is by design, not a delay.Triggers
The trigger_type decides when a contact enrolls, and trigger_config names the specific list, tag, or form to match. There are three trigger types, each with its own one-field config:
| trigger_type | trigger_config | Enrolls when |
|---|---|---|
| list_added | { "list_id": "list_…" } | A contact is added to that contact list (including bulk list-adds and imports). |
| tag_added | { "tag": "vip" } | That exact tag (trimmed, case-sensitive) is applied to a contact. |
| form_signup | { "form_id": "form_…" } | A contact is created or confirmed via that signup form. |
list_id and form_id must belong to your account, or the request fails with invalid_request (400). For tag_added, the tag string just has to be non-empty — tags are not pre-registered.Steps
steps is the ordered sequence each enrollment walks, top to bottom. There are two step types, distinguished by type. You can mix them freely and use up to 100 steps.
Wait step
A wait step parks the enrollment for a fixed number of hours before the next step runs. hours must be greater than 0 (and at most one year, 8760). Use waits to pace a sequence — a day between emails, a week before a follow-up.
{ "type": "wait", "hours": 24 }Email step
An email step sends one email to the enrolled contact. It requires a subject and at least one of body_html or body_text — supply both for the HTML and plain-text parts. The from_email comes from the automation, not the step. All three fields support {{ variables }} drawn from the contact.
{
"type": "email",
"subject": "Welcome aboard, {{ first_name }}",
"body_html": "<h1>Hi {{ first_name }}</h1><p>Glad you joined.</p>",
"body_text": "Hi {{ first_name }} — glad you joined."
}wait step with hours ≤ 0, an email step with no subject, or an email with neither body_html nor body_text is rejected with invalid_request (400). An unknown type is rejected too.Create an automation
POST /v1/automations (admin scope) creates an automation in draft. Give it a from_email on a verified domain, a trigger, and your steps. Nothing happens until you activate it.
Request body
The response is the created automation, always in status: "draft" with enrollment_count: 0.
{
"id": "auto_a1b2c3",
"name": "Welcome series",
"trigger_type": "list_added",
"trigger_config": { "list_id": "list_9f21" },
"from_email": "[email protected]",
"status": "draft",
"steps": [
{ "type": "email", "subject": "Welcome aboard, {{ first_name }}", "body_html": "<h1>Hi {{ first_name }}</h1><p>Glad you are here.</p>" },
{ "type": "wait", "hours": 24 },
{ "type": "email", "subject": "Getting started", "body_html": "<p>Here is how to make the most of it.</p>" }
],
"enrollment_count": 0,
"created_at": "2026-06-14T10:00:00Z",
"updated_at": "2026-06-14T10:00:00Z"
}trigger_config always comes back as an object — an empty {} if you sent none — and enrollment_count is the number of contacts currently enrolled (zero on a fresh automation).How enrollment works
When an automation is active and its trigger fires for a contact whose trigger_config matches, that contact is enrolled at step 0. From there a background scheduler claims due enrollments and advances each one step at a time: a wait step parks the enrollment until its hours elapse, an email step sends, and running past the last step completes the enrollment.
- Enroll once — a contact enrolls in a given automation at most once. Re-triggering (added to the same list again, re-tagged) is intentionally a no-op; it never starts a second run of the same sequence.
- Consent at send time — before each email step, the scheduler re-reads the contact. If they have no email, or have unsubscribed or been suppressed, the enrollment is cancelled rather than sent. Consent is checked when the email actually goes out, not when the contact enrolled.
- Suppression & idempotency — because each email runs the standard send pipeline, suppression-list addresses are skipped and every step gets a deterministic idempotency key, so a worker retry never double-sends a step to the same person.
- Unsubscribe footer — automation emails are
marketingmail, so each one carries a visible unsubscribe footer in the body and one-clickList-Unsubscribeheaders. An opt-out cancels any further steps for that contact.
List automations
GET /v1/automations (read scope) returns your automations, newest first, under an automations array. Each entry includes its live enrollment_count.
curl https://api.sendara.dev/v1/automations \
-H "Authorization: Bearer sk_live_xxx"{
"automations": [
{
"id": "auto_a1b2c3",
"name": "Welcome series",
"trigger_type": "list_added",
"trigger_config": { "list_id": "list_9f21" },
"from_email": "[email protected]",
"status": "active",
"steps": [ { "type": "email", "subject": "Welcome aboard, {{ first_name }}" }, { "type": "wait", "hours": 24 } ],
"enrollment_count": 412,
"created_at": "2026-06-14T10:00:00Z",
"updated_at": "2026-06-14T10:05:00Z"
}
]
}Get an automation
GET /v1/automations/{id} (read scope) returns a single automation with its full step list. An unknown id returns not_found (404).
curl https://api.sendara.dev/v1/automations/auto_a1b2c3 \
-H "Authorization: Bearer sk_live_xxx"Update an automation
PUT /v1/automations/{id} (admin scope) edits an automation. Send only the fields you want to change; anything you omit is left as-is. Note that steps is replaced wholesale when present — the array you send becomes the new sequence.
curl -X PUT https://api.sendara.dev/v1/automations/auto_a1b2c3 \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Welcome series v2",
"steps": [
{ "type": "email", "subject": "Welcome aboard, {{ first_name }}", "body_html": "<h1>Hi {{ first_name }}</h1>" },
{ "type": "wait", "hours": 48 },
{ "type": "email", "subject": "Two days in", "body_html": "<p>How is it going?</p>" }
]
}'trigger_type or trigger_config re-runs the same target validation as create — a foreign or unknown list_id / form_id is rejected with invalid_request (400). Editing steps does not re-enroll anyone; contacts already in the sequence simply follow the new steps from where they are.Activate
POST /v1/automations/{id}/activate (admin scope) moves an automation to active. From this moment, matching contacts are enrolled as their trigger fires and the scheduler begins advancing enrollments. The response is the updated automation.
curl -X POST https://api.sendara.dev/v1/automations/auto_a1b2c3/activate \
-H "Authorization: Bearer sk_live_xxx"{ "id": "auto_a1b2c3", "status": "active" }Pause
POST /v1/automations/{id}/pause (admin scope) moves an automation to paused. New enrollments stop, and in-flight enrollments stop advancing until you activate again — at which point they resume from where they parked.
curl -X POST https://api.sendara.dev/v1/automations/auto_a1b2c3/pause \
-H "Authorization: Bearer sk_live_xxx"{ "id": "auto_a1b2c3", "status": "paused" }Delete an automation
DELETE /v1/automations/{id} (admin scope) permanently removes an automation and returns 204 No Content. Its enrollment rows are removed with it, so any contacts still mid-sequence stop receiving further steps.
curl -X DELETE https://api.sendara.dev/v1/automations/auto_a1b2c3 \
-H "Authorization: Bearer sk_live_xxx"Errors
Automation endpoints use the standard error envelope. The codes you'll see most:
| Code | Status | When it fires |
|---|---|---|
| invalid_request | 400 | Missing from_email, an unknown trigger_type, a trigger_config whose list_id / form_id / tag is missing or foreign, or an invalid step. |
| not_found | 404 | No automation with that id exists for your account. |
| unauthorized | 401 | Missing or invalid API key. |
| forbidden | 403 | Key lacks the required scope — read to list/get, admin to create/update/delete/activate/pause. |
Scopes
Listing and reading automations requires a key with the read scope. Creating, updating, deleting, activating, and pausing all require the admin scope. See Authentication for how scopes work.