# SendLang

> Two small languages for lifecycle email — SendQL says who a message is for, SendFlow says what happens to them over time.

Source: https://www.sendlang.com/docs
Section: Introduction

SendLang is the home of two languages that do one job each.

**SendQL** is a query language for people. One SendQL query is a single boolean
expression, and it answers one question: *which contacts does this match?*

```sendql
attr.plan = "trial" and count(open within 14d) >= 1 and not suppressed
```

**SendFlow** is a workflow language. One SendFlow file is one campaign, read top
to bottom, and it answers a different question: *what happens to a contact, and
when?*

```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"
  }
}
```

The two are not siblings that happen to share a vendor. **SendFlow is a strict
superset of SendQL.** Every `where`, `when`, `if` and `until` in a workflow is a
real SendQL predicate, parsed by the same grammar and checked by the same type
checker — not a quoted string that gets evaluated somewhere else later. Learn
SendQL and you have already learned every condition SendFlow can express.

## Why a language and not a canvas

Most lifecycle email tools give you a drag-and-drop canvas. A canvas is a fine
way to *read* a campaign and a bad way to *own* one: you cannot diff it, you
cannot review it, you cannot roll it back to what it was on Tuesday, and you
cannot ask an agent to change it.

A language is text. Text goes in git.

- **It reviews like code.** A change to who gets a discount shows up in a pull
  request, with an author, a diff and an approver.
- **It fails loudly.** A misspelled attribute is a build error with a line and a
  column, not a campaign that quietly sends to nobody for a week.
- **A coding agent can write it.** This is not a footnote — it is one of the
  reasons the languages exist. A published grammar, a type checker to iterate
  against, and a canonical formatter are exactly the surface an agent needs to
  author and refactor a campaign the way it edits any other code, with you
  reading the diff before anything sends. See
  [coding agents](/docs/agents).

None of this costs you the canvas. A SendFlow file maps losslessly onto a
flowchart and back — sequence, branch, bounded repeat, timed wait — so a visual
editor and a text file stay two views of one thing.

## What they deliberately do not do

Neither language evaluates anything. They parse, they analyze, they print. There
is no `goto`, no unbounded loop, no arbitrary expression evaluation and no way to
call out to your code. A workflow always terminates, and a segment is always a
pure predicate over data you already have.

That is not an oversight; it is the point. The restriction is what lets a
workflow be drawn as a diagram, statically checked, and safely run for a hundred
thousand contacts at once.

## Where to go next

- New here? [Core concepts](/docs/concepts) is the ten-minute version of the
  data model both languages sit on.
- Writing a segment? Start with [SendQL](/docs/sendql).
- Writing a campaign? Start with [SendFlow](/docs/sendflow).
- Pointing an agent at this? [Coding agents](/docs/agents) is the page to give
  it — and `/llms-full.txt` is the whole reference in one file.
- Want the normative spec? The [grammar](/docs/reference/grammar) is generated
  from the parsers themselves.
