Open sourceSendQL and SendFlow are now public

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
trial-onboarding.flowSendFlow
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 logoCloud 66
  • SendOps logoSendOps
  • Adze logoAdze

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 who

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

segment.sendqlSendQL
attr.plan = "pro"
  and attr.country in ["US", "CA", "MX"]
  and not suppressed

SendFlow

the what

A drip-workflow language stored in .flow files. It reads top to bottom and embeds SendQL for every condition.

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

winback.flow+3−1
·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.
segments.sendqlSendQL
// 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 suppressed
cart-abandonment.flowSendFlow
workflow "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

  1. Entercart_updated
  2. Wait1 hour
  3. Sendcart-reminder
  4. repeat up to 2 · every 24h · until order
    Sendcart-nudge
  5. 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

engaged.sendqlSendQL
count(open within 14d) >= 1

The same predicate, inside a workflow

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

Terminal
$ go get github.com/altacoda/sendql
$ go get github.com/altacoda/sendflow