Developer guideAutomations
Campaigns
Couryo campaigns send one email to every contact or to a segment by attributes and tags, with scheduling, tests, a review before sending, pause and delivery, open and click stats. No charge per contact.
A campaign sends one email to an audience of contacts: every subscribed contact of the project, or a segment. It is the list send (newsletter, launch, promotion, notice), always on the marketing stream, with one-click unsubscribe and the suppression list respected.
No charge per contact. Campaign emails count in the plan quota and nothing else.
Who can send#
| Plan | Campaigns |
|---|---|
| Free | builds and tests drafts; does not send |
| Pro | with the Automations add-on (US$ 9 / R$ 49 per month) |
| Scale and Enterprise | included |
You also need trust level 1 or more (a verified domain). Without it, sending answers 403 campaigns_not_available, with upgrade_options showing what unlocks it.
Audience#
- Every subscribed contact (
{ "type": "all" }). - A segment (
{ "type": "segment", "match": "all" | "any", "conditions": [...] }): up to 20 conditions, all of them (AND) or at least one (OR).
field |
op |
Example |
|---|---|---|
attribute (with key) |
eq, neq, contains, exists |
{ "field": "attribute", "key": "plan", "op": "eq", "value": "pro" } |
tag |
eq (has the tag), neq (does not), contains, exists (has any) |
{ "field": "tag", "op": "eq", "value": "customer" } |
email |
eq, neq, contains, exists |
{ "field": "email", "op": "contains", "value": "@company.com" } |
- Comparisons ignore case.
neqalso matches contacts without the attribute. - Anyone who unsubscribed, bounced, complained or was suppressed by hand is left out on their own.
- Contact tags come in
tagson the contact. POST /v1/contacts/countwith{ "audience": ... }counts the audience without sending:{ total, subscribed, suppressed }.- The audience is fixed when sending starts: whoever joins later does not get this campaign.
Create#
curl https://api.couryo.com/v1/campaigns \
-H "Authorization: Bearer $COURYO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: october-campaign" \
-d '{
"name": "October collection",
"from": "Shop <news@shop.com>",
"audience": { "type": "segment", "match": "all",
"conditions": [{ "field": "tag", "op": "eq", "value": "customer" }] },
"subject": "Hi {{name|there}}: the new collection is here",
"html": "<p>Hi {{name|there}}!</p><p><a href=\"https://shop.com/collection\">See the collection</a></p>"
}'- Content: a saved template (
template_id) or a subject with your ownhtmland/ortext, made in the editor. With a template,subject(when sent) replaces the template subject. - Variables: the contact attributes (
{{name}}, also{{contact.name}}),{{email}}, the fixed values invariablesand{{unsubscribe_url}}. Use a fallback for contacts without the attribute:{{name|there}}. Without a value and without a fallback, the passage comes out empty; an empty subject for one contact is an error for that contact only. - Unsubscribe: the unsubscribe link is added at the end of the email when the content has none, and the one-click
List-Unsubscribe(RFC 8058) always goes. PATCH /v1/campaigns/{id}changes drafts and scheduled campaigns. On a paused campaign, the content changes for who is left, but the audience stays fixed.
Before sending#
POST /v1/campaigns/{id}/preflight sends nothing and returns:
recipients: the real recipient count right now;suppressed: how many are left out;never_emailedandnever_emailed_share: who never got an email from this project. Above 30%, thenever_emailedwarning reminds you that a cold list hurts delivery;needs_review: whether it goes to review (below);review: the free AI review of the content;warningsandcan_send: what stops sending (plan, level, sender, domain, content, empty audience).
POST /v1/campaigns/{id}/test sends the real content, with [Test] in the subject, to up to 5 addresses of the people in your account ({ "to": ["you@shop.com"] }). Tests are not counted in the stats.
Send, schedule, pause#
| Call | What it does |
|---|---|
POST /v1/campaigns/{id}/send |
starts now |
POST /v1/campaigns/{id}/schedule with { "scheduled_at" } |
starts at the set time (up to 90 days) |
POST /v1/campaigns/{id}/pause |
stops between batches; works on scheduled ones too |
POST /v1/campaigns/{id}/resume |
continues where it stopped (plan and level checked again) |
POST /v1/campaigns/{id}/cancel |
who has not received it will not; what went out stays in the stats |
- Sending goes out in batches, at the engine's pace and within your level limits. When the daily limit runs out, the campaign waits and continues on its own;
status_messageexplains. - Each contact gets it once, even if the server restarts in the middle of a batch.
- Statuses:
draft,scheduled,in_review,sending,paused,sent,cancelled. A paused one haspaused_reason:manual,bounce_rate,limit,domain,planorcontent.
Safeguards#
- First large campaign: the account's first campaign above 5,000 recipients gets a review before it starts (
status: in_review). The agent releases clear cases right away (level 2 or more, a list with sending history, clean rates); the others get a human look, usually within 1 business day. The answer comes by email. After that, the next ones go straight through. - Automatic pause on bounces: if more than 5% of the first thousand emails bounce, the campaign pauses on its own (
paused_reason: bounce_rate) and you get an email with the numbers. The bounced addresses are already suppressed; clean the list before resuming. - The account rules still apply on top: gradual descent, marketing pause and review.
Stats#
GET /v1/campaigns/{id}/stats:
{
"campaign_id": "cmp_8k2m4q9w1x7z3abc",
"recipients": 1203, "sent": 1198, "delivered": 1180, "bounced": 18, "complained": 1,
"opened": 484, "opened_machine": 170, "clicked": 79, "unsubscribed": 5, "skipped": 5, "failed": 0,
"rates": { "delivery": 0.985, "bounce": 0.015, "complaint": 0.0008, "open": 0.4102, "click": 0.0669, "click_to_open": 0.1632, "unsubscribe": 0.0042 },
"top_links": [{ "url": "https://shop.com/collection", "clicks": 66, "total": 91 }],
"opens_estimated": true
}- Opens are estimated (see Open and click tracking).
opened_machinecounts the ones opened only by the mail program's privacy protection. clicksintop_linksare people (unique emails);totalis every click.- Every campaign email carries the
campaigntag with the id, so it shows inGET /v1/emails?tag=campaign:cmp_...and in webhooks.
In the dashboard and for agents#
In the dashboard, Campaigns builds it in steps (audience, content, review, send) and shows the campaign page with the stats. On MCP, the tools create_campaign, list_campaigns, get_campaign_stats, send_campaign and pause_campaign; send_campaign only sends with confirm: true (without it, it returns the checks and the count).