# Sequences

> Event-triggered email sequences in Couryo, with triggers, steps (send, wait, condition, exit), automatic exits, quiet hours and a per-contact limit. No charge per contact.

Source: https://couryo.com/en/docs/sequences

A **sequence** sends a series of emails to each contact that enters it: welcome on sign-up, trial reminders, abandoned cart. You build the steps as a list in the dashboard (under **Sequences**) or through the API, and Couryo takes care of timing, conditions and exits.

**No charge per contact.** Sequence emails count in the plan quota and nothing else.

## Triggers

- **Event:** the contact enters when an [event](https://couryo.com/en/docs/contacts.md) with that name arrives (`POST /v1/events` or the MCP `track_event` tool). Each contact enters a sequence **once**.
- **Manual:** you put the contact in from the dashboard or with `POST /v1/sequences/{id}/enrollments`. A contact that finished can enter again this way.
- **Conversion event** (optional): when it arrives (for example `order.paid`), the contact leaves the sequence as **converted**.

## Steps

| Step | What it does |
|---|---|
| `send` | sends an email: a [saved template](https://couryo.com/en/docs/templates.md) (`template_id` + `variables`) or its own subject, HTML and text, made in the [editor](https://couryo.com/en/docs/template-editor.md) |
| `wait` | waits minutes, hours or days (`amount`, `unit`); with `until`, then waits until HH:mm in the account's or the contact's time zone (`timezone`: `account` or `contact`) |
| `condition` | checks whether the contact opened or clicked an earlier email, whether an event happened since entry, or whether an attribute equals a value; `if_true` and `if_false` continue (`next`), exit (`exit`) or jump to a later step (`{ "goto": "<step id>" }`) |
| `exit` | the contact leaves the sequence there |

In the emails you can use the contact attributes (`{{name}}`, also as `{{contact.name}}`), `{{email}}`, the properties of the event that started the sequence (`{{event.plan}}`) and `{{unsubscribe_url}}`.

### A contact without an attribute

Not every contact has every attribute, and that does not break the sequence:

- A variable with a [fallback](https://couryo.com/en/docs/templates.md#fallback-variablefallback) uses it: `Hi {{name|there}}!` reads "Hi there!" for a contact without `name`.
- A variable **without a value and without a fallback comes out empty** and the email is still sent, both with its own content and with a template. The enrollment gets a `warning` (for example, "Variables without a value in step 1: name"), shown in the enrollments list of the dashboard, and the contact **stays** in the sequence.
- Only an email that is **really empty** is an error: an empty subject after filling the variables, or empty content. Then the contact leaves with reason `send_failed` and the explanation in `last_error`.
- In the builder, the dashboard warns when a step uses a variable without a fallback: "Contacts without `{{x}}` get this passage empty. Use `{{x|fallback}}`."
- Sending through the API (`POST /v1/emails` with `template` or `variables`) stays strict: a variable without a value and without a fallback is refused.

```bash title="Create a sequence"
curl https://api.couryo.com/v1/sequences \
  -H "Authorization: Bearer $COURYO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Onboarding",
    "trigger": { "type": "event", "event": "user.signed_up" },
    "conversion_event": "order.paid",
    "from": "Shop <hi@example.com>",
    "steps": [
      { "id": "welcome", "type": "send", "template_id": "welcome" },
      { "type": "wait", "amount": 2, "unit": "days", "until": "09:00" },
      { "type": "condition", "check": { "kind": "opened", "step_id": "welcome" }, "if_true": "next", "if_false": "exit" },
      { "type": "send", "subject": "A tip for you, {{name}}", "html": "<p>Hi {{name}}!</p>" }
    ]
  }'
```

The sequence starts as a draft. Activate it with `POST /v1/sequences/{id}/activate`: it needs a sender on a verified domain and at least one email.

## Automatic exits

The contact leaves the sequence, and no other email of it goes out, when:

- they **unsubscribe** (one-click link or mailbox provider request): reason `unsubscribed`;
- the address goes on the **suppression list**: `suppressed`;
- there is a **hard bounce**: `bounced`, or a **spam complaint**: `complained`;
- the **conversion event** arrives: the status becomes `converted`;
- the sequence is **deleted** (`sequence_deleted`) or **paused** with `exit_enrollments: true` (`sequence_paused`). Paused without that option, nobody gets anything and each contact continues where it stopped when it comes back.

Other reasons: `condition` (the condition said exit), `exit_step`, `removed` (you took it out), `contact_deleted` and `send_failed`.

## Sending rules

- **Quiet hours:** by default no sequence email goes out from 21:00 to 08:00 in the account time zone; the email waits for the end of the window. Set it in `settings.quiet_hours` (or `null` to turn it off).
- **At most 1 sequence email per contact every 12 hours**, across all sequences of the project. Set it in `settings.min_hours_between_emails`.
- **Marketing stream by default**, with one-click unsubscribe (`List-Unsubscribe`) and an unsubscribe link **added at the end of the email when the content has none**. The `transactional` stream is only for **account onboarding** sequences, which start from an event.
- **Everything goes through the same path as `POST /v1/emails`:** plan quota, [trust levels](https://couryo.com/en/docs/limits.md), suppressions and the content check. If a daily limit or a pause holds the send, Couryo retries every hour for up to 3 days.
- **No duplicate sends:** each step runs once per contact, even if the server restarts in the middle.

## Plans

| Plan | Sequences |
|---|---|
| Free | 1 active sequence, with up to 3 emails |
| Pro | same as Free; with the **Automations add-on**, unlimited |
| Scale and Enterprise | unlimited, included |

Drafts do not count. Activating beyond the limit returns `403` [`automations_limit_reached`](https://couryo.com/en/docs/errors.md#automations_limit_reached), with `upgrade_options` (the add-on and Scale, with prices). The add-on is turned on and off under **Billing** in the dashboard.

## Metrics and enrollments

- `GET /v1/sequences/{id}/metrics` returns, per step: sent, delivered, opens, clicks, exits and conversions.
- `GET /v1/sequences/{id}/enrollments` lists who is in or went through the sequence (`status`: `active`, `completed`, `exited`, `converted`), with the current step, the next send (`next_run_at`), the exit reason, the last error (`last_error`) and the `warning`, such as variables that came out empty. Filter by `status` and part of the email (`q`).

## Endpoints

| Method and path | Scope | What it does |
|---|---|---|
| `GET` and `POST /v1/sequences` | `read` / `admin` | lists and creates (draft) |
| `GET`, `PATCH` and `DELETE /v1/sequences/{id}` | `read` / `admin` | one sequence, update (`steps` replaces the whole list) and delete |
| `POST /v1/sequences/{id}/activate` | `admin` | activates or resumes |
| `POST /v1/sequences/{id}/pause` | `admin` | pauses; `{ "exit_enrollments": true }` also takes everyone out |
| `POST /v1/sequences/{id}/duplicate` | `admin` | copy as a draft |
| `GET /v1/sequences/{id}/metrics` | `read` | metrics per step |
| `GET` and `POST /v1/sequences/{id}/enrollments` | `read` / `send` | lists and puts a contact in by hand |
| `GET` and `DELETE /v1/sequences/{id}/enrollments/{eid}` | `read` / `admin` | one enrollment and taking the contact out |

Through [MCP](https://couryo.com/en/docs/mcp.md): `create_sequence` (scope `admin`, creates a draft) and `list_sequences` (`read`).
