Skip to content
couryo

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.

View as Markdown

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. neq also 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 tags on the contact.
  • POST /v1/contacts/count with { "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#

Create the campaign (draft)
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 own html and/or text, 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 in variables and {{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_emailed and never_emailed_share: who never got an email from this project. Above 30%, the never_emailed warning reminds you that a cold list hurts delivery;
  • needs_review: whether it goes to review (below);
  • review: the free AI review of the content;
  • warnings and can_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_message explains.
  • 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 has paused_reason: manual, bounce_rate, limit, domain, plan or content.

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:

Response
{
  "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_machine counts the ones opened only by the mail program's privacy protection.
  • clicks in top_links are people (unique emails); total is every click.
  • Every campaign email carries the campaign tag with the id, so it shows in GET /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).