Marketing

Automations

Drip sequences and triggered journeys — enroll on a signal, then deliver the right email at the right hour, automatically.

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.

Automations are email-only. There is no SMS or voice channel — an email step sends an email and nothing else. For one-off transactional mail, use 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.

Automation statuses
StatusMeaning
draftThe default on create. Fully editable. No contacts enroll and nothing sends — a safe place to build the sequence.
activeLive. Matching contacts are enrolled as triggers fire, and the scheduler advances every enrollment through the steps.
pausedFrozen. No new enrollments, and in-flight enrollments stop advancing — they resume from where they parked when you reactivate.
Until you call /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 types and config
trigger_typetrigger_configEnrolls 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.
The referenced target is validated at create and update time: 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."
}
Step validation is strict. A 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

from_emailstringRequired
The sending address for every email step. Must be on a verified domain, otherwise each enrollment cancels at send time with from_not_verified.
trigger_typestringRequired
What enrolls a contact: list_added, tag_added, or form_signup. Anything else returns invalid_request.
trigger_configobjectRequired
The trigger target — { list_id } for list_added, { tag } for tag_added, { form_id } for form_signup. The referenced list or form must belong to your account.
namestringOptional
A human label, shown in the dashboard and list responses. Defaults to "Untitled automation" when omitted.
stepsarrayOptional
The ordered sequence the enrollment walks. Up to 100 steps, each a wait or an email step (see Steps). An empty array creates an automation that simply enrolls and completes.

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 marketing mail, so each one carries a visible unsubscribe footer in the body and one-click List-Unsubscribe headers. An opt-out cancels any further steps for that contact.
Editing or pausing takes effect between steps. Because the scheduler advances one step per claim and re-checks the automation each time, a contact already mid-sequence will pick up your edited steps — and a pause stops them at the current step — without restarting the journey.

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>" }
    ]
  }'
namestringOptional
New label. Omit to leave unchanged.
from_emailstringOptional
New sending address. Cannot be set to an empty string.
trigger_typestringOptional
New trigger type. If you change this, re-check trigger_config.
trigger_configobjectOptional
New trigger target. Re-validated against the (possibly new) trigger_type.
stepsarrayOptional
A replacement step list. The supplied array fully replaces the existing one — it is not a patch.
Changing 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" }
Activation is forward-looking: it enrolls contacts whose trigger fires after you activate. It does not retroactively sweep in everyone already on the list or carrying the tag.

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"
Deleting an automation removes the sequence and its enrollments. The individual emails it already sent — and their event history — are retained under messages, so deletion never erases your delivery audit trail.

Errors

Automation endpoints use the standard error envelope. The codes you'll see most:

Automation error codes
CodeStatusWhen it fires
invalid_request400Missing from_email, an unknown trigger_type, a trigger_config whose list_id / form_id / tag is missing or foreign, or an invalid step.
not_found404No automation with that id exists for your account.
unauthorized401Missing or invalid API key.
forbidden403Key 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.