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.
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 in your agent's context. That is both languages, complete, in one fetch.
Writing SendLang with a coding agent 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.