# SendLang: A DSL for Email Automation

> Source: <https://www.sendlang.com>
> Published: 2026-07-15 15:23:43+00:00

# A language layer for lifecycle email

SendLang is the home of two small, focused languages. SendQL describes who a message is for. SendFlow describes what happens to them over time — and speaks SendQL natively.

Written by people *and by coding agents* — a published grammar, a type checker to iterate against, and a diff you approve. [See how](#agents).

SendLang

## One family, two languages

SendQL is the noun. SendFlow is the verb. Together they turn contact data and event streams into campaigns you can read in a diff.

### SendQL

the whoA segment-definition query language. Every query is a single boolean expression describing which contacts a segment matches.

### SendFlow

the whatA drip-workflow language stored in `.flow`

files. It reads top to bottom and embeds SendQL for every condition.

Why a language

## Your lifecycle email, in version control

Every other tool locks your cohorts and lifecycle campaigns inside a visual editor. Make them a language instead, and they become text you can diff, review, and hand to a coding agent.

- It lives in version control
- Every segment and workflow is a plain text file. Diff it, review it in a pull request, roll it back. Your targeting and your drip logic earn the same rigor as the rest of your code.
- It fails before it sends
- A misspelled attribute is a type 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 the big one. A canvas is a dead end for an agent; a language is the loop it already works in. Describe the outcome, let it draft the file, merge it once it passes review.

Coding agents

## Hand the campaign to an agent

A canvas is a dead end for a coding agent — it cannot read it, change it, or tell you what it changed. A language is the loop an agent already works in: read the file, write the file, run the checker, open a pull request. That is not a side effect of the design. It is a reason for it.

“Three days before a trial ends, email them. If they have not clicked within three days, send the upgrade offer.”

- The docs are machine-readable
- Every page has a Markdown twin, and llms.txt / llms-full.txt hand an agent the whole of both languages in a single fetch. Nothing to scrape, nothing to guess at.
- A grammar, not a guess
- The normative EBNF and the reserved-word list are emitted by the parsers themselves, so the spec an agent reads cannot drift from the code that judges what it writes.
- The type checker is the feedback loop
- Every parse and type error carries a line, a column, and a message that names the fix. The agent iterates against the checker until the run is clean — and nothing sends until it is.
- You are still the approver
- No goto, no unbounded loops, nothing to evaluate. The worst an agent can hand you is a workflow that terminates — and you read it as a diff before it ever reaches a contact.

The shortest useful thing you can do: put [/llms-full.txt](/llms-full.txt) in your agent's context. That is both languages, complete, in one fetch.

[Writing SendLang with a coding agent](/docs/agents)

SendQL

## Describe exactly who you mean

SendQL reads events as a first-class source alongside contact attributes — the gap left by engines that see only precomputed rollups, never the raw stream.

- Attributes and lists
- Match on contact data with operators, in [...] sets, contains / starts with / ends with, and list or segment membership.
- Events as a first-class source
- Query the raw event stream directly — count, exists, sum, avg, last, first — not just precomputed engagement rollups.
- Consent and suppression
- Express deliverability rules inline: subscribed to "topic", opted out of "topic", unsubscribed from all, not suppressed.
- Windows and recency
- Bind a time window to any event source with within 30d or between two dates, and reason about recency with now - 14d.

The same file, rendered as a canvas

- Entercart_updated
- Wait1 hour
- Sendcart-reminder
- repeat up to 2 · every 24h · until orderSendcart-nudge
- Exitpurchased

SendFlow

## Structure, no goto

A workflow reads top to bottom and maps losslessly to a flowchart — sequence, waits, branches, weighted splits, and one bounded repeat. Never a goto, never an unbounded loop.

- One trigger, many exits
- Enter on a segment, an activity, an email event, or a duration before a contact's own date. Named exits report conversions the moment they happen.
- Timed steps
- wait 2d, wait until a date or attribute, or wait up to 7d until a condition — with an optional timeout arm.
- Branch, split, hold out
- Fall-through if / else if / else, weighted split { 30%: ... 70%: ... } that must sum to 100, and hold out 10%.
- Bounded loops only
- The single loop is repeat up to N every duration until a condition. No goto, no unbounded loops, ever.

How they fit

## SendFlow speaks SendQL

The predicate you write to define a segment is the very same syntax that drives every condition inside a workflow.

A SendQL segment

The same predicate, inside a workflow

- A strict superset
- One shared token stream
- One type-checker and diagnostics

Design

## Built like languages, not config

Both languages are versioned by a profile number, split cleanly from their execution engines, and specified down to a machine-checked grammar.

- Structured by design
- Sequence, branch, bounded repeat, timed waits, named exits — a control-flow subset that maps losslessly to a flowchart.
- Events as first class
- Selection over the raw event stream sits alongside contact attributes, so you can segment on behavior, not just state.
- Golden-pinned grammar
- A normative EBNF is generated from the parser and pinned by tests, so the spec can never silently drift from the code.
- Precise diagnostics
- Parsing and type-checking are separate stages, and every error carries a line and column pointing at the exact token.
- A canonical formatter
- SendFlow ships a one-true-layout printer — gofmt for flows — so every workflow in your repository reads the same way.
- A canvas, losslessly
- The control flow is restricted enough that a workflow maps onto a flowchart and back with nothing lost — comments included. Text and canvas are two views of one thing.

Documentation

## The whole language, written down

Every construct in both languages, every diagnostic they can produce, and a normative grammar generated from the parsers themselves — so the spec can never quietly drift from the code.
