Send a campaign
The flow is always the same: audience, content, estimate, send. The estimate is not optional unless you want to discover the real volume after hitting send.
1. Build the audience
Contacts, one at a time or in batches:
upsert_contact { email, attributes: { name: "Sara", plan: "pro" } }
It is idempotent on the email: calling it again updates instead of duplicating. The
attributes are merged with the existing ones, and passing null on a key
deletes that key.
Then a static list:
create_list { name: "Pro customers" }
add_contacts_to_list { listId, contactIds }
Or a dynamic segment, which recomputes itself:
create_segment { name, rules }
estimate_segment { segmentId } → how many people it covers right now
To find ids without guessing them: list_contacts, list_lists, list_segments,
list_tags.
2. Create the campaign
create_campaign {
name: "October launch",
subject: "Something new for you",
html: "...",
listId | segmentId
}
3. Read it back and fix it
get_campaign { campaignId }
update_campaign { campaignId, subject?, html?, ... }
If a detail is wrong, fix the campaign instead of creating a second one: abandoned drafts stay around and confuse whoever opens the dashboard after you.
4. Estimate, then send
estimate_campaign { campaignId } → real recipients, plus a confirmationToken
send_campaign { campaignId, confirmationToken }
confirmationToken proves the estimate was actually seen; it expires after 15
minutes, so re-estimate if you sat on it. This is required for an immediate send
because there is no window to undo it once it starts.
To schedule instead of sending now, pass sendAt at least 5 minutes in the
future and skip the token:
send_campaign { campaignId, sendAt: "2026-10-01T12:00:00Z" }
A sendAt sooner than that, in the past, or missing entirely falls back to
requiring confirmationToken: nothing about "send" defaults to immediate
without proof someone looked at the numbers first. list_campaigns shows the
state of the existing ones.
There is no MCP tool to stop a campaign once it is scheduled or sending: that
still needs POST /campaigns/:id/cancel on the REST API, or the dashboard. It
stops the recipients not yet queued; a large campaign mid-send can still be
cut short, a small one usually cannot (it finishes in seconds).
What Sending always filters out
The estimated number is almost always lower than the list total, and that is correct:
- contacts without consent for the channel,
- addresses on the suppression list (earlier bounces or complaints),
- recipients already reached by an identical idempotent send.
Do not try to work around these filters: they protect the domain reputation, which is the deliverability of every campaign that comes after.
Quota
send_campaign is subject to the plan quota. get_usage shows what is left of the
month: call it before preparing a large send, not after.

