A language layer for lifecycle email
SendLang is the open-source 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 in Go
- Embeddable parsers
- Powers SendOps
workflow "Trial onboarding" v1 {
enter on segment "trial-started"
exit "converted" when attr.plan != "trial"
send "welcome" via topic onboarding
wait 2 days
if not exists(open where template = "welcome") {
send "welcome-reminder" via topic onboarding
wait 2 days
}
send "activation-tips" via topic onboarding
wait up to 7 days until count(login within 7d) >= 2 {
timeout: send "need-a-hand" via topic onboarding
}
}Already in production at
Cloud 66
SendOps
Adze
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.
attr.plan = "pro"
and attr.country in ["US", "CA", "MX"]
and not suppressedSendFlow
the whatA drip-workflow language stored in .flow files. It reads top to bottom and embeds SendQL for every condition.
enter on segment "trial-started"
wait 2 days
if not exists(open within 2d) {
send "nudge" via topic onboarding
}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.
workflow "Winback" v1 { enter on segment "inactive-90d" exit "reactivated" when count(open within 14d) >= 1 send "we-miss-you" via topic marketing wait 3 days wait up to 5 days until exists(click within 5d) { timeout: send "last-call" via topic marketing }}- 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.
- Coding agents can write it
- Because these are real languages with a grammar and diagnostics, a coding agent can author and refactor segments and flows for you — exactly the way it writes code.
- Automate the automation
- Stop clicking through a canvas to ship a campaign. Describe the outcome, let an agent draft the change, and merge it once it passes review.
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.
// Paid accounts in North America
attr.plan = "pro" and attr.country in ["US", "CA", "MX"]
// Opened at least 3 times in the last 30 days
count(open within 30d) >= 3
// Clicked the pricing page this week
exists(click where url contains "/pricing" within 7d)
// Signed up 3–14 days ago, still on free
now - attr.signup_date between 3d and 14d and attr.plan = "free"
// Subscribed, and safe to send
subscribed to "product-updates" and not suppressedworkflow "Cart abandonment" v1 {
enter on event cart_updated where not exists(order within 1h)
exit "purchased" when exists(order)
wait 1 hour
hold out 10%
send "cart-reminder" via topic marketing
repeat up to 2 every 24 hours until exists(order) {
send "cart-nudge" via topic marketing
}
}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 event, or N days before a date. Add named exits that report conversions the moment they happen.
- Timed steps
- wait 2 days, wait until a date or attribute, or wait up to 7 days 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
count(open within 14d) >= 1The same predicate, inside a workflow
workflow "Winback" v1 {
enter on segment "inactive-90d"
exit "reactivated" when count(open within 14d) >= 1
send "we-miss-you" via topic marketing
wait up to 5 days until exists(click within 5d) {
timeout: send "last-call" via topic marketing
}
}- 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 front end, not lock-in
- The languages only parse, analyze, and print. Evaluation lives in a separate engine, so you can embed the parsers anywhere.
Open source
Open to the public
Read the grammar, embed the parsers, or open a pull request. SendQL and SendFlow are yours to build on.
$ go get github.com/altacoda/sendql
$ go get github.com/altacoda/sendflow