# SendFlow

> A workflow language for lifecycle email. One file is one campaign, read top to bottom.

Source: https://www.sendlang.com/docs/sendflow
Section: SendFlow

A SendFlow file is one campaign. It reads top to bottom, it always terminates,
and it maps losslessly onto a flowchart.

```sendflow trial-onboarding.flow
workflow "Trial onboarding" v1 {
  enter on segment "trial-started"
  exit "converted" when attr.plan != "trial"

  send "welcome" via topic "onboarding"
  wait 2d
  if not exists(open where template = "welcome") {
    send "welcome-reminder" via topic "onboarding"
    wait 2d
  }
  send "activation-tips" via topic "onboarding"
  wait up to 7d until count(activity.login within 7d) >= 2 {
    timeout: send "need-a-hand" via topic "onboarding"
  }
}
```

## Anatomy

Every workflow has the same three-part shape.

**Syntax form, not a complete program:**

```sendflow
workflow "<name>" v<N> {
  <settings>      // who enters, who leaves, when you may send

  <statements>    // what happens, in order
}
```

**The header** names the workflow and pins the language profile it was written
against. `v1` is not a version of *your* campaign — it is the version of SendFlow
itself, so a file keeps meaning what it meant when you wrote it. See
[versioning](/docs/reference/versioning).

**Settings** describe the shape of the campaign: exactly one trigger, any number
of named exits, and optional rules about send windows, re-entry, frequency and
back-enrolment. Settings must all come **before** the first statement — a setting
after a statement is a parse error, not a lint.

**Statements** are the campaign itself, executed in order for each contact who
enters.

## Conditions are SendQL

Every `where`, `when`, `if` and `until` in the file above is a real
[SendQL](/docs/sendql) predicate — parsed by the same grammar, checked by the same
type checker, reported with the same errors. Not a string. Not a mini-language.
The same language.

**Syntax form, not a complete program:**

```sendflow
enter on segment "x" where attr.plan = "pro"      // a predicate
exit "converted" when attr.plan != "trial"        // a predicate
if count(open within 7d) >= 1 { ... }             // a predicate
wait up to 7d until exists(activity.order) { }    // a predicate
```

If you can write a segment, you can already write every condition SendFlow has.

## Structured, with no goto

The control flow is deliberately small:

- **sequence** — one statement after another
- **branch** — `if` / `else if` / `else`, whose arms rejoin afterwards
- **split** — a weighted random fork, for A/B tests and holdouts
- **bounded repeat** — `repeat up to N every <duration>`, the only loop
- **timed waits** — for a duration, until a date, or until a condition

There is no `goto`, no jump, no unbounded loop and no recursion. A `repeat`
*must* declare a maximum count. This is not a limitation you will run into; it is
the property that makes a workflow drawable as a diagram, checkable before it
runs, and impossible to accidentally turn into an infinite mail loop.

## Exits happen everywhere

A named exit is not a statement you place somewhere. It is a **setting**, and it
is re-evaluated before every step:

**Syntax form, not a complete program:**

```sendflow
exit "converted" when attr.plan != "trial"
```

The moment that predicate becomes true, the contact leaves — whichever step they
were on, whether they were mid-`wait`, mid-`repeat`, or about to receive the next
send. The name is what shows up in conversion reporting.

This is why you rarely need to guard your sends with `if` conditions checking
whether the goal has already been met. Declare the goal once, at the top, and the
engine handles the rest.

## The file on a canvas

Because the control flow is structured, a `.flow` file and a flowchart are two
views of one thing. Every workflow projects to a graph and back with nothing
lost — including your comments. A visual editor is not a separate source of
truth that has to be reconciled with the text; it *is* the text.

## Next

- [Settings](/docs/sendflow/settings) — triggers, exits, windows, caps, enrolment
- [Statements](/docs/sendflow/statements) — send, wait, branch, split, repeat
- [Conditions](/docs/sendflow/conditions) — how SendQL embeds
- [Cookbook](/docs/sendflow/cookbook) — complete, working campaigns
