---
name: build-automation
order: 3
description: "Create, fix and activate a graph automation (welcome, abandoned cart, follow-up) with Sending, enroll contacts by hand, and read the logs to find out why a contact never got a message. Use this for recurring sends triggered by an event, and before changing automations that are already live."
---

# Build an automation

An automation is a graph (a DAG): a trigger, then wait, condition, action and
message nodes. It lives in two states: **draft** and **active version**.

## 1. Look at what is already there

```
list_automations
```

Do this **always, before creating or activating**. It also shows the **triggers of
the published version** (in `triggers`: there can be more than one), and that is the
piece of information that prevents damage: triggers like `contact.list_added` and
`contact.tag_added` start real sends.

If you add a contact to a list while an active automation is hooked to that list,
you have just sent. The same goes for `tag_contacts`.

## 2. Create the draft

The body is a Journey: `name`, `entry` (id of the starting node) and `nodes`. Every
node has an `id`, a `type` and the edges to other ids (or `null`, which ends the
branch).

```
create_automation {
  journey: {
    name: "Welcome",
    entry: "t1",
    nodes: [
      { id: "t1", type: "trigger", event: "contact.list_added",
        match: { listId: "<uuid>" }, next: "tag1" },
      { id: "tag1", type: "add_tag", tagIds: ["<uuid>"], next: "w1" },
      { id: "w1", type: "wait", duration: "P1D", next: "m1" },
      { id: "m1", type: "message", preferredChannel: "email",
        fallbackChain: ["email"], templateByChannel: { email: "<uuid>" }, next: "done" },
      { id: "done", type: "exit" }
    ]
  }
}
```

This creates the draft and version 1. **Nothing is sent yet.**

The uuids of tags, lists, templates and automations come from `list_tags`,
`list_lists` and `list_automations`: do not invent them.

### Available nodes

**Sending** · `message` (email/WhatsApp/Telegram with fallback, optional
`attachmentIds` and `ai`), `notify` (email to your own team, not to the contact).

**Flow** · `wait` (exactly one of `duration` ISO-8601, `until` date, or
`untilCondition`), `wait_for_event`, `branch` (`onTrue`/`onFalse`), `split`
(percentages across 2 to 4 branches), `goto` (jump to another step),
`start_automation`, `end_automation`, `webhook`, `goal`, `exit`.

**Contact** · `add_tag`, `remove_tag`, `subscribe`, `unsubscribe`, `update_contact`
(`null` deletes a key), `add_note`, `score`, `conversion`.

The complete list with the fields of each one is in the description of
`create_automation`: read it instead of guessing.

### Several entry triggers

An automation can start from **several events**: add more `trigger` nodes, each with
its own `next`. The contact enters through the trigger that matches the event, so
two entry points can lead to two different branches or to the same step.

```
{ id: "t1", type: "trigger", event: "contact.list_added", match: { listId: "<uuid>" }, next: "w1" },
{ id: "t2", type: "trigger", event: "cart.abandoned", next: "w1" },
```

`entry` must be the id of **a trigger**: it is where contacts enrolled by hand start
from, when no event is in play.

### Three rules the save enforces

You need **at least one trigger**, two triggers with the same event and the same
`match` are refused (the second would add nothing), and a trigger other than the
`entry` must have a `next`: a disconnected one would do nothing.

**Cycles are allowed only through a `goto` node** (with a ceiling of 50 hops per
contact). A loop made of `next` edges chasing each other is refused: it is almost
always a writing mistake.

Careful with `add_tag` and `subscribe`: they re-emit `contact.tag_added` and
`contact.list_added`, so they **can start another automation**. Check
`list_automations` before using them.

## 3. Fix without recreating

```
get_automation { automationId }      → reads back the published Journey
update_automation { automationId, journey }   → publishes a new version
```

Always start from the DAG that `get_automation` returns: rebuilding one from scratch
loses the parts you did not know about. Contacts already inside stay on the version
they entered with; the change applies to new entries.

## 4. Activate, but only with the user's consent

```
activate_automation { automationId }
```

From here on, every contact that satisfies the trigger enters the flow and receives
real messages. Have the user confirm explicitly before calling it: this is the point
of no return.

## 5. Enroll contacts by hand

This is for people who are already there, without waiting for the event to happen
again (a recovery campaign, a realignment after an import):

```
enroll_contacts { automationId, listId }        // or segmentId, or contactIds
```

One audience at a time. It works only on **active** automations, and the contacts
enter from the `entry`, so **the steps that send do send**: ask for confirmation as
you would for `activate_automation`. Anyone already inside is ignored (one
enrollment per contact) and templates using `{{event_*}}` find empty values, because
no event fired here.

The operation is asynchronous: the response says it has been queued. For the outcome
use `get_automation_log`.

## Events

`wait_for_event` nodes and custom entries are fed by the events API:

```http
POST https://sending.dev/api/v1/events
{ "name": "cart.abandoned", "email": "sara@acme.io", "properties": { ... } }
```

An event can bring new contacts into the flow and release the ones waiting on a
`wait_for_event`.

## Working out what happened

When a contact "got nothing", do not guess:

```
get_automation_log { automationId, email: "sara@acme.io" }
```

It lists the steps that were walked, with their outcome. A **`skipped` status is not
an error**: it says what stopped that step, and it is almost always the answer
(address on the suppression list, plan quota reached, missing template or resource).
With `nodeId` you narrow it to a single step of the flow.

For the overall effect on one person:

```
get_contact_journey { email }
```

It returns messages, inbound mail, touches and conversions in one timeline, telling
transactional apart from marketing.

## Attachments in nodes

The `message` node carries `attachmentIds`: upload first with `upload_attachment`
and put the id there. Do not delete those attachments while the automation is
active, or the deferred sends will break.
