Lifecycle email
from your terminal.
The open source Customer.io alternative, built for developers and AI agents.
$ npx @subscriber.dev/cli login## how it works
- 01
sign in
From your agent or your terminal. A browser opens once to confirm, and that's the only time you'll need it.
$ claude mcp add --transport http subscriber https://mcp.subscriber.dev → opening browser to sign in… ✓ connected as [email protected]
$ subscriber keys create "production" ✓ sk_env_•••• · store it as SUBSCRIBER_API_KEY
$ subscriber login → opening browser to sign in… ✓ logged in as [email protected]
$ subscriber → opening browser to sign in… ✓ signed in, opening the TUI
- 02
connect your email provider
Resend, Postmark, Amazon SES, Cloudflare or any SMTP server. You keep your sending reputation and pay them directly.
> connect Resend ● subscriber · set_integration ✓ resend connected
$ curl -X PATCH https://api.subscriber.dev/integrations/resend \ -H "x-api-key: $KEY" -d '{…}'$ subscriber integrations set resend --body '{…}'$ subscriber Integrations › n new › Resend
- 03
create a workflow
Describe it to your agent, or write HTML with {{ subscriber.firstName }} personalisation. Start it from a Stripe event, and stop it the moment someone converts.
> send new signups 3 onboarding emails over a week ● subscriber · create_workflow ✓ onboarding is live
$ curl -X POST https://api.subscriber.dev/workflows \ -H "x-api-key: $KEY" -d @onboarding.json
$ subscriber workflows create --file onboarding.json
$ subscriber Workflows › n new
- 04
trigger it
Usually from your app's code with one API call, or let an integration like Stripe trigger it. Your agent and the CLI can too.
> trigger welcome for [email protected] ● subscriber · trigger_workflow ✓ run started
// in your app, e.g. after signup await fetch(`https://api.subscriber.dev/workflows/trigger`, { method: "POST", headers: { "x-api-key": KEY, "content-type": "application/json" }, body: JSON.stringify({ key: "welcome", subscriber: { email: user.email } }), });$ subscriber workflows trigger welcome --email [email protected]
$ subscriber Workflows › welcome › t trigger
## emails that stop when they convert
Start a workflow on one event and stop it on another, so nobody gets "your trial ends soon" after they've paid.
> stop trial onboarding when they pay ● subscriber · update_workflow ✓ trial-onboarding exits on stripe/trial_converted
$ curl -X PATCH $API/workflows/t5r8hm2xq9vb -H "x-api-key: $KEY" \
-d '{"exitConditions":[{"source":"stripe","event":"trial_converted"}]}'$ subscriber workflows update t5r8hm2xq9vb --file exit.json
$ subscriber Workflows › Trial onboarding › g triggers › + Add exit condition › [x] Trial converted to paid
[email protected] · trial-onboarding day 0 ● stripe/trial_started run started day 0 → welcome to your trial ✓ sent day 3 → get the most out of it ✓ sent day 5 ● stripe/trial_converted ✕ exit condition · run cancelled day 7 → your trial ends soon – skipped
8 Stripe events, from trial_started to payment_failed. More integrations on the way.
## broadcasts
One-off sends to everyone, or anyone matching a tag or attribute. Preview the audience first, spread sends over hours to protect your sending reputation, and pause or cancel while it's going out.
> send the october changelog to everyone who's opted in, spread over two days ● subscriber · preview_broadcast audience opted-in subscribers matched 32,480 ● subscriber · trigger_broadcast send over 48 hours > how did it go? ● subscriber · get_broadcast ✓ done: 31,884 sent, 486 bounced, 86 skipped, 24 failed (99.9% success rate).# changelog workflow # API=https://api.subscriber.dev/workflows/p7d2nx9c4kwe $ curl -X POST $API/broadcast -H "x-api-key: $KEY" \ -d '{"audience":{"type":"optedIn"},"sendOverHours":48}' { "broadcastId": "k3x9vq2m7tpa", "status": "queued" } $ curl $API/broadcasts/k3x9vq2m7tpa -H "x-api-key: $KEY" { "status": "completed", "matchedCount": 32480, "sentCount": 31884, "bouncedCount": 486, "skippedCount": 86, "failedCount": 24, "pendingCount": 0, … }$ subscriber workflows broadcast p7d2nx9c4kwe \ --audience opted-in.json --send-over-hours 48 { broadcastId: 'k3x9vq2m7tpa', status: 'queued' } $ subscriber workflows broadcasts p7d2nx9c4kwe --json [ { "publicId": "k3x9vq2m7tpa", "status": "completed", "matchedCount": 32480, "sendOverHours": 48, … } ]$ subscriber Broadcasts › changelog › b broadcast audience All opted-in subscribers send over 48 hours Broadcast queued: k3x9vq2m7tpa h broadcasts › completed — 32480 matched — triggered 2 days ago Broadcast status: completed matched: 32480 send window: spread over 48h Step outcomes: sent: 31884 failed: 24 skipped: 86 pending: 0 bounced: 486 (hard 401, soft 85) success rate: 99.9% p pause · r resume · c cancel · s set up missing · b back · q quit
## why subscriber.dev
› built for agents
An MCP server, API, CLI and TUI. Set up and run everything without opening a dashboard.
› bring your own provider
Send through Resend, Postmark, Amazon SES, Cloudflare or any SMTP server. You keep your sending reputation and there's no markup on every email.
› event-driven
Events from your integrations start workflows, and exit conditions stop them the moment someone converts. Stripe today, more on the way.
› open source
AGPL-3.0 and self-hostable, so you're never locked in.
## every run, explained
Ask why someone did or didn't get an email. Every run keeps what was sent, what was skipped and why.
> why didn't [email protected] get the trial reminder? ● subscriber · list_environment_runs ● subscriber · list_inbound_events henry upgraded on day 5. stripe/trial_converted is an exit condition on trial onboarding, so the run was cancelled before "your trial ends soon" went out. Working as intended.$ curl "$API/workflows/runs?limit=1" -H "x-api-key: $KEY" { "runs": [{ "publicId": "r8k2v5n1x7qd", "status": "cancelled", "workflow": { "key": "trial-onboarding", … }, "subscriber": { "email": "[email protected]", … }, "steps": [ { "stepOrder": 0, "status": "sent", … }, { "stepOrder": 1, "status": "sent", … }, { "stepOrder": 2, "status": "cancelled", "errorMessage": "Exited on Stripe trial_converted", … } ] }] }$ subscriber workflows run-history --limit 1 [{ publicId: 'r8k2v5n1x7qd', status: 'cancelled', workflow: { key: 'trial-onboarding', … }, subscriber: { email: '[email protected]', … }, steps: [ { stepOrder: 0, status: 'sent', … }, { stepOrder: 1, status: 'sent', … }, { stepOrder: 2, status: 'cancelled', errorMessage: 'Exited on Stripe trial_converted', … } ] }]$ subscriber Run History › [email protected] Run r8k2v5n1x7qd workflow: Trial onboarding subscriber: [email protected] status: cancelled triggered: 6 days ago completed: 1 day ago Steps: 0. email — sent (sent 6 days ago) 1. email — sent (sent 3 days ago) 2. email — cancelled — Exited on Stripe trial_converted b back · q quit
## priced by subscribers. send as much as you like.
Only subscribers who can receive email count. Free includes 10,000 sends a month; paid plans don't limit sends. Emails go out through your own provider, at its prices.
subscribers 10,000 plan growth · $39/mo · no send limit
┌──────────┬─────────────┬────────────┐ │ plan │ subscribers │ per month │ ├──────────┼─────────────┼────────────┤ │ free │ 1,000 │ $0 │ │ starter │ 5,000 │ $19 │ │ growth │ 10,000 │ $39 │ │ scale │ 25,000 │ $79 │ │ pro │ 50,000 │ $159 │ │ business │ 100,000 │ $279 │ │ custom │ 100,000+ │ talk to us │ └──────────┴─────────────┴────────────┘
self-hosting: community (free) · business ($999/yr) · enterprise (talk to us)
## faq
+−is there a web interface?
Not a dashboard. Everything works through the API, the CLI and TUI, or your AI agent over MCP.
+−can I edit emails without code?
Yes. Ask your agent to rewrite it, or open the email in the web editor to tweak the copy and preview it before it sends. It's the only place you'll leave the terminal, and only if you want to.
+−which AI agents does it work with?
Any MCP client: Claude, Claude Code, Cursor, ChatGPT, Codex and others. Add the server, sign in once in the browser, and your agent can create workflows, manage subscribers and send broadcasts.
+−which email providers can I use?
Resend, Postmark, Amazon SES and Cloudflare Email are built in, with bounces and spam complaints handled automatically. Anything else with SMTP works too, such as SendGrid or Mailgun. You pay your provider directly, usually a few dollars a month. compare providers and costs
+−how do you handle deliverability?
Every email gets one-click unsubscribe headers, which Gmail and Yahoo require of bulk senders, plus an unsubscribe link. Unsubscribed people are never sent to, hard bounces unsubscribe automatically, and every preference change is kept. Mail goes out through your own provider and domain, so SPF, DKIM and your reputation stay yours.
+−can I move from Loops or Customer.io?
Yes. Import subscribers through the bulk API or subscriber subscribers bulk, 500 at a time, or hand your agent an export and let it do the import. Describe your existing sequences and it can rebuild them as workflows.
+−can I get my data out?
Any time. Everything is available through the API and CLI, and because it's open source you can self-host and take the whole database with you.
+−can I self-host it?
Yes. It's open source under AGPL-3.0 and runs with Docker Compose. Self-hosting is free and unlimited. A $999/yr self-hosted Business license removes our branding and includes priority support, and Enterprise licenses cover AGPL exemptions.
+−why did you build subscriber.dev?
We needed lifecycle email for Kan, and the options meant paying per contact and being locked into one email provider (usually SES at a large markup). We wanted our agent to set up and run it, sending through our own provider, and like Kan, we wanted it open source. Nothing did all three, so we built it.
+−who's behind it?
Open Engineering, the team behind Kan, the open source Trello alternative. Kan's lifecycle and changelog emails, over 50,000 a month, all run on subscriber.dev.
## start in one command
Free up to 1,000 subscribers · your provider · your agent
$ npx @subscriber.dev/cli login$ claude mcp add --transport http subscriber https://mcp.subscriber.dev