Browse the docs

Introduction

SendLang

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

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
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.

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 is the ten-minute version of the data model both languages sit on.
  • Writing a segment? Start with SendQL.
  • Writing a campaign? Start with SendFlow.
  • Pointing an agent at this? Coding agents is the page to give it — and /llms-full.txt is the whole reference in one file.
  • Want the normative spec? The grammar is generated from the parsers themselves.