---
title: "How automations work"
description: "An automation mails people one at a time as things happen: somebody joins a list, fills in a form, does something in your product, or has a birthday. You describe the path once and each person walks it at their own pace."
url: "https://openemail.uk/docs/api/automations/overview"
area: "API"
category: "Mailbox"
---

# How automations work

An automation mails people one at a time as things happen: somebody joins a list, fills in a form, does something in your product, or has a birthday. You describe the path once and each person walks it at their own pace.

## A trigger and a tree of steps

A `definition` has a `trigger`, the `entry` step and a list of `steps`. Each step has a `key` that is unique in the automation: a lower-case letter followed by 2 to 23 lower-case letters or digits. A step names the one that follows it in `next`, and a `branch` names two, `yes` and `no`. `null` ends that path. The steps form a tree, so no step is reached from two places and nothing loops back. An automation holds up to 50 steps, with branches at most 5 deep.

- `audience_joined`: a contact is added to `audienceId`. Contacts added by an import are left out unless `includeImported` is true.
- `form_submitted`: a person signs up through the form `formId`. With double opt-in, they enter when they confirm.
- `event`: your code sends an event named `eventName`. Up to 5 `filters` on its properties narrow who enters.
- `date`: a day arrives for each member of `audienceId`. `field` is `birthday` or `joined`, the anniversary of the day they joined that audience. `offsetDays` moves it by up to a year: negative for days before, positive for days after.
- `manual`: nobody enters by themselves. You add people from the app or with the enroll endpoint.

| Step | What it does |
| --- | --- |
| `send_email` | Sends the published version of `templateId` from `from`, an address of this workspace. `props` fill the template values, and `subject` replaces the template subject and takes merge fields such as `{{firstName\|there}}` |
| `wait` | Holds the person: for a `duration`, `until` the next given weekday and time, or for an `event` they have to do, with a `timeout` after which they carry on anyway |
| `branch` | Asks one question and sends the person down `yes` or `no`: `email_opened` or `email_clicked` for an earlier email step, `in_audience`, a contact `field`, or an `event` they did within `withinDays` |
| `add_to_audience`, `remove_from_audience` | Changes which audiences the contact is in |
| `update_field` | Writes a value onto the contact |
| `webhook` | Calls one of your webhook endpoints with an `automation.webhook` event |
| `exit` | Ends the path early. It counts as leaving, not as finishing |

A value in `props` or `update_field` comes from one of three places: `{ "source": "static", "value": "…" }`, `{ "source": "contact", "field": "firstName" }` for `email`, `name`, `firstName`, `lastName` or `attributes.<key>`, and `{ "source": "event", "path": "orderId" }` for a property of the event that started the run.

## A draft and a live version

Saving changes the draft `definition`. Nothing runs until `POST /automations/{id}/publish` freezes the draft as a numbered version, which `published` then shows. People who are already inside finish on the version they entered with, and people who enter afterwards get the new one. `hasUnpublishedChanges` says the draft has moved on.

- `draft`: never published. Nobody enters.
- `live`: published and running.
- `paused`: nobody enters and everybody inside holds where they are. `pausedReason` says why: `manual`, or a problem the engine met, such as `sender_refused` or `template_unavailable`.
- `archived`: retired for good. Everybody inside leaves, and the history stays.

`settings` are separate and apply as soon as they are saved: the `timezone`, a `sendWindow` outside which emails wait, `reentryDays` before the same person may enter again (`null` means once), `exitOnLeave` to drop people who leave the trigger audience, and `listAudienceId`, the audience an unsubscribe is recorded on.

`problems` lists what is wrong with the draft, each with a `code`, the `path` of the field, the `stepKey` and whether it is `blocking`. A draft with a blocking problem cannot be published.

## People inside an automation

Each person who enters gets an enrollment. It is `active` while they move through the steps, `completed` when they run off the end of a path, and `exited` when they leave early, with an `exitReason`: `exit_step`, `unsubscribed`, `suppressed`, `left_audience`, `removed`, `archived` or `failed`.

- Steps run within about 15 seconds of becoming due. A person never gets two emails from one automation in the same pass.
- Automation emails are marketing mail, so every one carries an unsubscribe link. A person who unsubscribes leaves the automations that mail that list, and one whose address bounced or complained leaves at their next step.
- An email whose address cannot receive mail is skipped, and the person carries on to the next step.
- When a send is refused for everybody, such as a sender that lost its domain or a template that was unpublished, the automation pauses and `pausedReason` says why.

## Events from your app

`POST /events` records that a contact did something: `order.placed`, `trial.started`, `plan.upgraded`. An event starts every live automation whose trigger names it, moves on anybody waiting for it, and answers the `event` question of a branch. Events are kept for 90 days.

## Who can do what

- Reading needs `automations:read` and changing needs `automations:write`. Publishing, resuming and sending a test also need `emails:send`, because they make the automation send mail.
- Sending an event needs `contacts:write`, and reading a contact’s events needs `contacts:read`.
- An API key and the owner see every automation in the workspace. An app a member connected sees the ones that member made.
- Deleting an automation asks an OAuth app for a verification code. An API key never needs one.
- A plan allows 1 live automation on Free, 10 on Starter, 50 on Business and any number on Enterprise. A workspace holds 100 automations by default.

`automation.entered`, `automation.exited` and `automation.paused` webhooks tell your systems who went in, who came out and when an automation stopped. Archiving an automation ends everybody in it without an `automation.exited` event for each person.

## From code, the terminal and agents

Everything here is also in the SDK as `openemail.automations` and `openemail.events`, and in the CLI as `openemail automations` and `openemail events`. The MCP server has automation tools, so an agent can build one, publish it and watch who is inside.
