Guides

Automations

Multi-step email programs triggered by a contact event — steps, waits, branches, and the forward-only rule that makes loops impossible.

An automation is a small program that runs per contact. You define an ordered list of steps — send, wait, branch, jump — and a trigger that enrolls contacts into it. emitd advances each enrollment independently.

Lifecycle

An automation is created as a draft. Only name and trigger_kind are required up front; everything else can be filled in with PATCH before you activate:

curl https://api.emitd.com/v1/automations \
  -H "Authorization: Bearer $EMITD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Welcome series", "trigger_kind": "contact_created" }'

Then activate to start enrolling, pause to stop advancing without losing state, and archive to retire it:

ActionEndpoint
ActivatePOST /v1/automations/{id}/activate
PausePOST /v1/automations/{id}/pause
ArchivePOST /v1/automations/{id}/archive

A verified domain is required to activate

from_domain_id must reference one of your verified sending domains before activate will succeed. Set it with PATCH while the automation is still a draft — see Sending domains.

Triggers

trigger_kindEnrolls whentrigger_config
contact_createdA contact is created in your workspaceignored
audience_addedA contact is added to a specific audience{"audience_id": "<id>"} — required
manualYou call the enroll endpoint yourselfignored

An audience_added trigger needs an audience you own — see Audiences. A trigger_config naming an audience that isn't yours is rejected with audience_not_found.

Manual enrollment

curl https://api.emitd.com/v1/automations/$ID/enroll \
  -H "Authorization: Bearer $EMITD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contact_id": "con_8Qd21k" }'

Enrollment is idempotent: a contact can only be enrolled in a given automation once, so re-enrolling is a no-op. A genuine enrollment returns 201; an existing one returns 200. You can retry this call safely without double-mailing anyone.

The automation must be active — enrolling into a draft or paused automation returns 409 with not_active. A contact_id that doesn't exist or isn't yours returns 422 with contact_not_found.

Steps

A program is an array of up to 50 steps. type discriminates the shape.

send

Enqueues one marketing send to the enrolled contact, then advances. subject is required and non-empty, and at least one of body_html / body_text is required.

{ "type": "send", "subject": "Welcome aboard", "body_html": "<h1>Hello</h1>" }

wait

Parks the enrollment for seconds, then advances. Range is 0 to 7776000 — 90 days.

{ "type": "wait", "seconds": 86400 }

branch

Evaluates a segment filter against the enrolled contact. On a match it advances to the next step; otherwise it jumps to else_to.

{
  "type": "branch",
  "filter": { "match": "all", "conditions": [
    { "field": "attributes.plan", "op": "eq", "value": "pro" }
  ]},
  "else_to": 4
}

goto

Jumps unconditionally to step index to.

{ "type": "goto", "to": 6 }

Loops are structurally impossible

Every jump target — branch.else_to and goto.to — must be strictly forward: greater than the jumping step's own index, and at most the program length. The length value is a sentinel meaning "end of program".

This is a deliberate design constraint, not a validation gap. It means an automation always terminates, so no enrollment can spin and no contact can be mailed in a cycle. If you are used to drip tools that let you loop back, model the repetition as explicit forward steps instead.

A backward or out-of-range target is rejected with bad_jump_target.

A complete program

A three-day onboarding series that skips the nudge for contacts who already upgraded:

PATCH /v1/automations/{id}
{
  "from_domain_id": "dom_4Kq9",
  "steps": [
    { "type": "send", "subject": "Welcome to Acme", "body_html": "<h1>You're in.</h1>" },
    { "type": "wait", "seconds": 259200 },
    { "type": "branch",
      "filter": { "match": "all", "conditions": [
        { "field": "attributes.plan", "op": "eq", "value": "free" }
      ]},
      "else_to": 5 },
    { "type": "send", "subject": "Ready to upgrade?", "body_html": "<p>Here's what Pro adds.</p>" },
    { "type": "goto", "to": 5 },
    { "type": "send", "subject": "Tips for week two", "body_html": "<p>Three things to try.</p>" }
  ]
}

Step indices are zero-based. The branch at index 2 sends free-plan contacts on to index 3 (the upgrade nudge) and everyone else to index 5, skipping it. The goto at index 4 rejoins the main line so the nudged contacts also reach index 5. Both jumps point forward, so the program is valid.

Validation errors

422 responses carry an error code naming exactly what was rejected:

CodeMeaning
invalid_namename missing or blank
invalid_trigger_kindnot one of the three kinds
missing_audience_idaudience_added without trigger_config.audience_id
audience_not_foundthe audience id is not yours
steps_not_arraysteps was not a JSON array
too_many_stepsmore than 50 steps
step_not_objecta step was not a JSON object
unknown_step_typetype is not send/wait/branch/goto
missing_fielda step omitted a required field
empty_subjecta send step's subject was blank
missing_bodya send step had neither body_html nor body_text
wait_out_of_rangeseconds outside 0–7776000
bad_jump_targeta jump was backward, self-referential, or past the end
bad_branch_filterthe branch filter failed segment-filter validation

Listing

GET /v1/automations returns a newest-first keyset page over (created_at, id). Pass the previous response's next_cursor as cursor to continue; a null cursor means the last page.

Full schemas: Automations API.

On this page