SendOps

Workflows

Drip campaigns and lifecycle automation on your own SES

Write workflows as readable text — SendFlow — or design them on a visual canvas. It's the same file, two views. Trigger on events, contact activity, and segment membership. Every email goes out through your own SES account: your infrastructure, your sending reputation, your rates — never shared sending infrastructure.

The problem

Lifecycle email lives in one of two bad places

The first is a journey builder you rent. You drag boxes around a canvas, the logic lives in someone else's database, and the bill is priced per contact — it grows with your list whether you send or not. There's no version history, no diff, no way to review a change before it goes live, and no way to move the campaign anywhere else. When the flow breaks, you find out from a customer, and the fix is more clicking.

The second is code you maintain: cron jobs, queues, a state machine per contact that someone built in a sprint two years ago. It's yours, but the drip logic is smeared across workers and database rows. Changing "wait 2 days" into "wait 3 days unless they've logged in" is a deploy, a migration, and a prayer for the contacts already mid-flow.

Neither one is reviewable, versioned, or operable by a coding agent. Click-ops can't be diffed. Homegrown queues can't be handed to marketing. Lifecycle email deserves the same treatment as the rest of your stack: a readable artifact, a review process, and infrastructure you already own.

SendFlow

A drip campaign you can read top to bottom

SendFlow is a small, purpose-built language for email drips. It's structured — sequence, if/else, bounded repeats, timed waits, named exits. No gotos, which is exactly what guarantees every flow maps losslessly onto a diagram. Conditions are SendQL, the same query language that powers your segments, embedded as real syntax rather than quoted strings.

  • One syntax tree powers both the text editor and the visual canvas — edit either, they stay in sync
  • Workflows live as .flow files in your repo — PR-reviewed, synced on merge — or edited in-app with typeahead
  • Waits, branches, holdout splits, and frequency caps are all part of the language, not settings hidden in a UI
flows/trial-onboarding.flow
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
  }
}

The canvas

The same workflow, drawn — not a second source of truth

The canvas isn't a separate builder that exports to code. It's the same file, rendered. Anything you can draw, the text can express — and the other way around.

  • Structure you can actually follow

    Repeats, branches, and splits render as framed containers holding their steps — nested, like the code — instead of a spaghetti of loop edges and reconverging arrows.

  • What you see is what you edit

    The read-only view and the editor are the same canvas. No mental translation between the diagram in the dashboard and the thing you change.

  • Inspect any node

    Click a send, a wait, or a branch to see its full configuration — the template, the topic, the SendQL condition — in a side panel.

  • Errors point at the source

    Validation errors are anchored to source lines, so a broken condition shows up on the exact node it belongs to — on the graph and in the text.

Engine & safety

Built for the ways lifecycle email goes wrong

The scary part of automation isn't writing the flow. It's what the engine does at 3am with your whole contact list. Every guardrail here exists because of a specific failure mode.

Triggers

Enroll contacts on events, contact activity, segment entry, or dates like a renewal. Enrollment is forward-only by default — activating a workflow never mass-enrolls everyone who matched last year. Backfilling history is an explicit opt-in.

Waits & durations

Wait 2 days. Wait until a date. Or wait up to 7 days until a condition is met, with a timeout branch for when it isn't. Send windows keep emails inside business hours in each contact's timezone.

Frequency caps

An account-wide cap limits how many workflow emails any contact receives in a window, counted across all workflows. A time-sensitive flow can override it in its own .flow file — frequency cap 4 per 7 days, or off.

Manual send approval

Flip a workflow into approval mode and its sends queue for human review instead of going out. Useful for the first run of anything new, or any flow where a mistake is expensive.

Consent on every send

Consent is re-checked at the moment each email is sent, not at enrollment. A contact who unsubscribes on day 3 of a 14-day drip stops hearing from you on day 3.

Full run observability

A per-node funnel over the live canvas, a timeline for every contact's run, exit breakdowns by name, and a dry-run mode that walks a test contact through the flow on a virtual clock — before anything real is enrolled.

Agents

Your coding agent can run lifecycle marketing

SendFlow and SendQL are plain text with a published grammar. That's the whole trick: Claude Code or Cursor can author and maintain drip campaigns the same way they author and maintain code — and your PR review is the guardrail, exactly where it already is for everything else.

01

The agent drafts

“Write an onboarding drip: welcome email, a reminder if it goes unopened, tips on day 4, a nudge if they haven't logged in twice by day 11.” The agent writes the .flow file in a branch.

02

SendOps validates

Syntax, SendQL conditions, and the templates and segments the flow references are all checked. A broken condition fails validation — it never fails in a customer's inbox.

03

Your team reviews

The diff is a readable text file, not a screenshot of a canvas. Anyone on the team can see exactly what changed: one wait, one condition, one template swap.

04

Merge deploys

On merge, the workflow syncs to your account and goes live. Rollback is a git revert. The campaign's entire history is in your repo, next to the code it serves.

A click-ops journey builder can't be delegated — there's nothing for an agent to write, diff, or revert. Files can.

Two surfaces, one workflow

Because the canvas and the text are the same file, nobody is locked out. Marketers get a visual builder, developers get files in CI, and neither one overwrites the other's work.

For Marketers

Build and edit flows on the visual canvas — drag steps into place, set conditions, adjust waits. No code required, and nothing you build is trapped: every canvas edit is the same reviewable file underneath.

For Developers

Campaigns as code. .flow files live in your repo, validate in CI, and deploy on merge — the same pull-request workflow you already run for templates and everything else. Rollback is a revert.

For Founders

Set up the lifecycle once — welcome, onboarding, win-back — and let it run. Frequency caps and consent checks keep it polite while you're not looking, and the funnel view tells you if it's working.

Automation you can read, review, and own

Free plan available. Connects to your SES account in two minutes.