> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sequenzy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sequences as Code

> Write sequences as YAML or JSON in your repository, push them to Sequenzy and keep the dashboard as your control room

You can define a sequence in a file in your repository, push it to Sequenzy and pull it back in the same shape. Your version control keeps every change, and the dashboard shows the live result: anyone on your team can open the sequence, check its numbers, turn it on or off and send tests.

A definition is one document per sequence. Steps run in the order you list them, if/else paths nest their own steps, and every step has a key you choose, which stays its identity for life.

## A definition file

```yaml sequences/onboarding.yaml theme={null}
key: onboarding
name: Onboarding
settings:
  stopCondition:
    type: has_tag
    value: customer
trigger:
  event: user.signed_up
steps:
  - key: welcome
    email:
      subject: Welcome aboard
      previewText: Here is how to get started
      html: "<p>Hi {{FIRST_NAME|there}}, thanks for signing up.</p>"
  - key: wait-2d
    wait: 2d
  - key: activated
    label: Created a project?
    branch:
      - if: { event: project.created }
        steps:
          - key: first-project-tips
            email:
              template: email_tips123
    else:
      - key: nudge
        email:
          subject: Need a hand getting started?
          html: "<p>Reply to this email and we will help.</p>"
  - key: wait-5d
    wait: 5d
  - key: case-study
    email:
      subject: How Acme sends 2x more email
      html: "<p>...</p>"
```

Contacts who signed up get the welcome email, wait two days, then get tips if they created a project or a nudge if they did not. Both paths continue to the 5-day wait and the case study, and the sequence ends after the last step.

To start a new file, run `sequenzy sequences new onboarding`. It writes a working example to `sequences/onboarding.yaml`.

### The trigger

| Trigger | Starts the sequence when |
| - | - |
| `event: user.signed_up` | the contact receives the event. Add `where:` with property filters to narrow it. |
| `tag: trial` | the tag is added to the contact. |
| `list: list_123` | the contact joins the list. |
| `segment: seg_123` | the contact enters the segment. |
| `manual: true` | you enroll contacts yourself, from the dashboard, the API or the CLI. |

Any other trigger is written as `type` and `config`, the way pulls return it. The trigger's key is `trigger` unless you set `key`.

### Steps

Each step has a `key` and one of these:

| Step | What it does |
| - | - |
| `wait: 1d 12h` | Waits. Use `w`, `d`, `h` and `m`. |
| `email:` | Sends an email. See [Emails](#emails). |
| `branch:` and `else:` | Sends each contact down the first path whose `if` matches, otherwise down `else`. |
| `split:` | Sends contacts down random paths, each with a `percent`. |
| `continueIf: { notTag: churned }` | Continues only for contacts that match, and ends the sequence for the rest. |
| `tag: activated` / `untag: trial` | Adds or removes a tag. |
| `end: true` | Ends the sequence at this point, for example at the end of one path. |
| `type` and `config` | Any other step type, written the way pulls return it. |

Any step can also take a `label`, shown on the step in the dashboard, and `config` with extra settings for its type.

A branch or split path lists its own steps, which can include more branches. When a path finishes, its contacts continue with the step after the branch. A path with no steps, or `else: []`, continues straight on. To leave a path early, end it with `end: true`, or point its last step somewhere else with `next: <step key>`.

### Conditions

Branch paths (`if`) and `continueIf` use the same conditions:

| Condition | Matches contacts who |
| - | - |
| `tag: customer` / `notTag: customer` | have, or do not have, the tag |
| `list: list_123` / `segment: seg_123` | are in the list or segment |
| `event: project.created` | have ever received the event |
| `field: plan` with `equals`, `contains`, `greaterThan` or `lessThan` | have a matching custom attribute |
| `clickedLink: true` or `clickedLink: pricing` | have ever clicked a link in your emails, or one whose URL contains the text |
| `hasPhone: true`, `smsSubscribed: true`, `hasPushDevice: true` | can be reached on that channel |

Give a path an `id` to keep its stats stable when you reorder paths; otherwise paths are numbered `branch-0`, `branch-1` and so on. Paths are checked in order, and the first match wins.

To check only activity since the contact entered the sequence, write the condition in its stored form, for example `if: { conditionType: link_clicked, activityScope: this_sequence }`. Pulls show any condition that has no shorthand in this form.

### Emails

An email step has a `subject`, an optional `previewText` and `name`, and its content as either `html` or `blocks`. Pulls always return `blocks`. Leaving out `name` or `previewText` keeps the stored value.

To reuse an email you designed in Sequenzy, name it with `template` and its ID, from `sequenzy templates list`:

```yaml theme={null}
- key: first-project-tips
  email:
    template: email_tips123
    subject: Your first project, sorted
```

Every push copies the template's subject, preview text, content and style into the step, and anything you write next to `template` replaces the template's value. The step keeps its own copy, so editing it in the sequence never changes the template. When the template changes, the next dry run shows the step's email as changed, and the push after it updates the step.

### Settings

`settings` holds the enrollment mode, sending window, stop condition, frequency cap, BCC addresses and sender and Reply-To profile IDs. A setting you leave out keeps its current value. The easiest way to get the exact shape is to set it in the dashboard once and pull the sequence.

## Pull and push

```bash theme={null}
# Start a new definition file
sequenzy sequences new onboarding

# Save one sequence, or every sequence that has a key
sequenzy sequences pull onboarding --dir sequences
sequenzy sequences pull --all --dir sequences

# See what a push would change, then push
sequenzy sequences push sequences --dry-run
sequenzy sequences push sequences
```

A pulled file pushed back unchanged changes nothing. A dry run lists the changed sequence fields, the added, changed and removed steps, and the added and removed connections, without saving anything.

Pulls return the format shown above. A sequence whose paths cross each other in a way nesting cannot describe is pulled in the explicit format instead, and so is any sequence when you pass `--raw`. See [The explicit format](#the-explicit-format).

To start managing an existing sequence from code, pull it by its ID with a key of your choice. Its steps get keys based on their content, such as `welcome-aboard` for an email with that subject or `wait-2d`, and the first push saves them without replacing any step:

```bash theme={null}
sequenzy sequences pull --id seq_123 --key onboarding --dir sequences
sequenzy sequences push sequences/onboarding.yaml
```

The same documents work through the [API](https://docs.sequenzy.com/api-reference/sequences/definitions.md) and the `get_sequence_definition` and `push_sequence_definition` MCP tools.

## What a push does

* **Steps are matched by key.** A new key adds a step, a changed step is updated in place, and a step missing from the file is removed. A step keeps its key for life, so its stats and history stay with it. Rename a step's `label`, never its key.
* **Contacts on a removed step move on.** They continue to the step that follows it, or complete when nothing follows. The dry run shows how many are waiting and where they go. If contacts are waiting on a removed step that could lead to more than one place, the push is refused until you connect it to a single next step.
* **A push never turns a sequence on or off.** New sequences start as drafts, and pushes leave the status and enrollment pause alone. Turn sequences on in the dashboard, with `sequenzy sequences enable` or with the API.
* **Changes to an active sequence need confirmation.** They affect current and future recipients, so pass `--confirm-live-change` (or `confirmLiveChange=true` in the API) after you have checked the dry run.
* **A push changes the whole sequence or none of it.** If anything in the file is invalid, the sequence is not changed, and the error names the place in the file, such as `steps[2].branch[0].if`. Tags and events named in the file may already have been created in your workspace. If someone edits the sequence while your push is running, the push fails instead of overwriting their change; push again.

## Managed by code

Sequences created by a push are **managed by code**. The dashboard shows their steps and settings read-only, with a note that they come from your definition, so the dashboard never drifts from your files. Everything else stays available to your whole team: turning the sequence on or off, pausing enrollment, test sends, enrolling contacts and the stats.

Set `managedBy: dashboard` in the definition to allow dashboard edits again, or have an owner or admin click **Unlock for dashboard editing** on the sequence. The next push still replaces the steps with what is in your file, so update the file or stop pushing it. Pulled files leave `managedBy` out, so pushing one never locks a sequence again; write `managedBy: code` in the file to lock it. Only owners and admins can change `managedBy` on an existing sequence.

The lock applies to the dashboard. The API and MCP update tools, such as `update_sequence`, can still change a code-managed sequence, and the next push replaces those changes with your file. Make the change in the file instead.

## The explicit format

The explicit format lists every step with its `type` and `config`, plus a `connections` list saying which step follows which. A document with `connections` is read in this format. It can describe any sequence, and it is what `--raw` and `?format=raw` return.

```yaml theme={null}
key: onboarding
name: Onboarding
steps:
  - key: trigger
    type: trigger_event
    config: { triggerType: event_received, eventName: user.signed_up }
  - key: welcome
    type: action_email
    config: {}
    email: { subject: Welcome aboard, html: "<p>Hi</p>" }
  - key: end
    type: action_webhook
    config: { isEndNode: true, label: Sequence completed }
connections:
  - { from: trigger, to: welcome }
  - { from: welcome, to: end }
```

Every sequence has one trigger step and at least one end step, an `action_webhook` with `isEndNode: true`. Branch steps (`logic_branch`) label each outgoing connection with a `condition` such as `{ branchId: else }`.

## Limits

* A/B test steps pass through a push unchanged: a push keeps their stored settings and cannot create or remove them. Manage A/B tests with the A/B test API or MCP tools, or in the dashboard while the sequence is not managed by code, then pull again.
* A push can only relabel an inbound webhook trigger; change its event and settings with `sequenzy sequences webhook configure`. A push cannot remove an inbound webhook trigger, replace it under another key, or switch a trigger to or from one.
* Email attachments that use `{{event.*}}` need an event-based trigger.
* Discount steps (`action_create_discount`) need `provider` and `discountType` written out.
* Webhook header values are pulled as `[REDACTED]`. Pushing them back unchanged keeps the stored values, so secrets stay out of your repository.
* A step's type cannot change under the same key, except between trigger types. Give the new step a new key instead.
* `end` is the key of the sequence's automatic end step; give your own steps other keys.
* A definition holds up to 200 steps.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.