Create, schedule, and track email campaigns from the CLI.
Campaigns
Send targeted email campaigns to segments of your users. Campaigns combine an email template with an audience filter and an optional schedule, so you can preview who will receive the email before it goes out.
Use cases: announce a new feature to active users, send a re-engagement email to churned users, distribute a newsletter on a schedule, or run a promotional campaign targeted by user attributes.
Campaigns vs journeys: which do you want?
A campaign is a broadcast: one message, one audience, sent once at a moment you choose. A journey is a reaction: a sequence triggered per user by something they did, running on their own clock.
Reach for a campaign when the timing is yours — a launch announcement, a newsletter, a pricing change. Reach for a journey when the timing is theirs — a welcome sequence on day 0, a nudge on day 3 if they have not finished setup, a win-back after 30 days idle.
The common mistake is building a recurring campaign to simulate onboarding. It works until users sign up at different times, which is immediately.
| Campaign | Journey | |
|---|---|---|
| Triggered by | A schedule you set | A user event or timer |
| Sends to | Everyone matching a filter, at once | One user at a time |
| Timing | Absolute (March 15, 10:00 UTC) | Relative (3 days after signup) |
| Typical use | Newsletter, launch, announcement | Onboarding, re-engagement, milestones |
| Repeats | No — create a new one | Yes, per user, continuously |
Both send through the same verified domain and address the same usr_ records, so there is no audience to sync between them.
Create a campaign
ascendkit campaign create \
--name "March Newsletter" \
--template tpl_abc123 \
--audience '{"tags":{"$in":["premium"]}}' \
--from [email protected] \
--scheduled-at 2026-03-15T10:00:00Z
| Flag | Description |
|---|---|
--name | Campaign name (required) |
--template | Template ID to use for the email body (required) |
--audience | JSON filter for targeting users (required) |
--from | Verified email identity to send from (optional; defaults to environment default) |
--scheduled-at | ISO 8601 datetime to send (optional; omit to create as draft) |
Writing an audience filter
--audience takes a JSON filter evaluated against user records. The simplest useful filter matches everyone:
--audience '{}'
Filters support MongoDB-style operators, which is what $in above is doing:
| Operator | Meaning | Example |
|---|---|---|
| exact value | Field equals value | {"plan":"pro"} |
$in | Field matches any listed value | {"tags":{"$in":["premium","beta"]}} |
$nin | Field matches none of the listed values | {"plan":{"$nin":["free"]}} |
$exists | Field is present or absent | {"company":{"$exists":true}} |
$gt / $lt | Greater or less than | {"signupCount":{"$gt":5}} |
Combine conditions by listing them together — all must match:
--audience '{"plan":"pro","tags":{"$in":["beta"]}}'
Always run campaign preview before scheduling. A filter that matches nothing and a filter that matches everyone both look correct in a shell. The preview is the only way to tell them apart before the send is irreversible.
Campaign lifecycle
A campaign moves through a fixed set of states, and what you are allowed to do depends on where it is:
| Status | Meaning | Can update? | Can cancel? |
|---|---|---|---|
draft | Created without a schedule | Yes | Yes — deletes it |
scheduled | Has a send time in the future | Yes | Yes — stops the send |
sending | Delivery in progress | No | Yes — stops remaining sends |
sent | Delivery complete | No | No |
failed | Delivery could not complete | Yes — then reschedule | Yes — deletes it |
cancelled | Stopped before completing | No | No |
Cancelling a sending campaign stops the remaining sends. It cannot recall messages already delivered.
List campaigns
ascendkit campaign list
ascendkit campaign list --status scheduled
Filter by status: draft, scheduled, sending, sent, failed, cancelled.
View a campaign
ascendkit campaign show cmp_abc123
Update a campaign
Update a draft, scheduled, or failed campaign before it sends:
ascendkit campaign update cmp_abc123 \
--name "Updated Newsletter" \
--template tpl_def456 \
--from [email protected]
Preview audience
See which users match the campaign's audience filter without sending:
ascendkit campaign preview cmp_abc123
Schedule a campaign
Set or change the send time for an existing campaign:
ascendkit campaign schedule cmp_abc123 --at 2026-03-20T14:00:00Z
Cancel a campaign
Delete a draft or failed campaign, or cancel a scheduled or sending campaign:
ascendkit campaign cancel cmp_abc123
View analytics
After a campaign is sent, check delivery and engagement metrics:
ascendkit campaign analytics cmp_abc123
The analytics output includes:
- Sent, delivered, failed, suppressed, and bounced delivery counts
- Opened and clicked engagement counts
- Unsubscribed and complained counts
- Calculated rates for open rate, click-to-open rate, bounce rate, and unsubscribe rate
Troubleshooting
The campaign sent to zero users
The audience filter matched nothing. Filters are evaluated exactly, so a field name typo or a value with different casing silently matches no one rather than erroring.
Run ascendkit campaign preview cmp_abc123 and compare the count against what you expect. If it is zero, check the field names against a real user record before adjusting the filter.
The campaign is stuck in sending
Large audiences deliver in batches, so sending is normal for a while. Check ascendkit campaign analytics cmp_abc123 — if delivered counts are still climbing, it is working.
If counts have stopped and the status has not moved to sent, some sends are failing. The analytics output separates failed, suppressed, and bounced, which tells you whether the problem is your domain, the recipients, or the content.
High bounce rate on the first campaign
Usually an unverified or newly verified sending domain. A domain with no sending history has no reputation, and providers treat the first large send from it with suspicion.
Verify your domain and confirm DKIM and SPF are configured before the first campaign. Sending progressively larger volumes rather than a single large first send also helps establish reputation.
Recipients report they never unsubscribed but stopped receiving email
Check the suppressed count in analytics. Addresses that hard-bounce or file a spam complaint are suppressed automatically to protect your sending reputation, which is separate from an explicit unsubscribe.
Related
- Templates — authoring and versioning the email body a campaign sends
- Journeys — event-triggered sequences, for when the timing belongs to the user rather than to you
- Email — domain verification, DKIM, and SPF, which decide whether any of this arrives
- Analytics — engagement data beyond a single campaign