A broadcast is one marketing email to an audience: a segment, a topic, or a segment narrowed to a topic's subscribers. You write a draft, send a test, then send it now or at a time. The platform takes a snapshot of who may receive it, sends one email per contact through the normal sending pipeline, and keeps count of what happened.

A draft

curl
curl https://api.minket.com/v1/broadcasts \
  -H "Authorization: Bearer $MINKET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "October update",
    "from": "Acme <news@news.acme.example>",
    "reply_to": "hello@acme.example",
    "subject": "What changed in October, {{first_name}}",
    "preview_text": "Three new features and one fix.",
    "segment_id": "019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5d",
    "topic_id": "019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
    "document": {
      "version": 1,
      "settings": { "footer": "Acme Ltd, 1 Example Street, Example City" },
      "blocks": [
        { "type": "heading", "level": "1", "text": "Hello {{first_name}}" },
        { "type": "paragraph", "text": "Here is what changed on your **{{plan}}** plan." }
      ]
    }
  }'
  • Content is a block document or html (plus an optional text part); new content of one kind replaces the other. Variables are the contact's fields and properties, escaped as in templates.
  • Every email ends with an unsubscribe link and a preferences link, signed for that contact (with the broadcast's topic, the unsubscribe link leaves only that topic). If the content places {{unsubscribe_url}} itself, only the preferences link is added.
  • from must be on a verified marketing domain of the project; a transactional domain is refused with stream_rule_violation. Marketing content checks apply.
  • PATCH changes a draft; DELETE removes a draft, a canceled or failed broadcast, or a scheduled one (it is canceled first).
  • POST /v1/broadcasts/{id}/duplicate copies any broadcast, a sent one included, into a new draft named <name> (copy): sender, reply-to, subject, preview text, audience, content, throttle and the A/B test's variants and settings (not its winner). It answers 201 with the draft; nothing about the original's sending carries over. To change a broadcast that is no longer a draft, duplicate it.

Who gets it

When sending starts, the audience is resolved to a snapshot (GET /v1/broadcasts/{id}/recipients) of the contacts who may receive marketing mail right then: subscribed (not unsubscribed), active (not bounced, complained or sunset), opted in to the topic (their own choice, else the topic's default) and with no marketing suppression in force. The console shows the same count live while you pick the audience.

Consent and suppression are checked again for each recipient when its email is created, and the send service checks suppressions, the topic and the kill switch once more. A contact who unsubscribed or was suppressed after the snapshot is skipped with a reason (unsubscribed, suppressed, topic_opt_out, the contact's status) and gets nothing.

Each email is a normal email (source: "broadcast", with broadcast_id, contact_id and topic_id), with its own timeline, events and webhooks. A recipient is marked sent in the same transaction that creates its email, so a step that runs twice never sends anyone twice.

Sending, scheduling and throttling

  • POST /v1/broadcasts/{id}/send answers 202 { "object": "broadcast", "id", "status" }. Without scheduled_at (as for emails, up to 30 days ahead) it starts now. Sending a scheduled broadcast again moves it to the new time.
  • POST /v1/broadcasts/{id}/cancel stops a scheduled, sending or paused broadcast at once: nothing more is sent, what was sent stays sent, and it cannot be resumed. Its emails that SES has not taken yet (held by the kill switch, or still queued) are canceled when the send worker reaches them, with an email.canceled event each, and their recipients are skipped with the reason canceled.
  • status moves through draft, scheduled, queued (the snapshot is being taken), testing (an A/B sample is out), sending, then sent. paused, canceled and failed are the other ends. broadcast.started and broadcast.completed events mark the edges.

Broadcasts are throttled per sending domain. Each one sends at most its throttle_per_minute (600 by default, 1 to 10,000), and every broadcast from the same domain draws on one per-minute budget. That budget never goes above half of the platform's sending rate, so transactional mail always has room.

Pauses

A broadcast pauses where it stopped, with the reason in error, when:

  • the project's kill switch is on: clear it, then send the broadcast again to resume;
  • the project reached its daily sending cap: it resumes by itself at midnight UTC, or raise the cap and send it again;
  • its domain stopped passing (not verified, authentication failing, sending turned off): fix the domain, then send it again.

Resuming carries on with the same snapshot; recipients already sent are never sent again.

A/B tests

JSON
{
  "ab_test": {
    "variants": [
      { "key": "a", "subject": "What changed in October" },
      { "key": "b", "subject": "Three new features" }
    ],
    "sample_percent": 20,
    "window_minutes": 120,
    "metric": "opens"
  }
}

sample_percent (5 to 50) of the snapshot is picked at random and split evenly between the two subjects. The sample's emails are tracked for the metric whatever the domain's tracking setting. Once the sample is out, the test window (window_minutes, 15 minutes to 24 hours) starts; when it ends, the subject with more unique opens (or clicks) per email sent wins (a tie goes to a), ab_test.winner and ab_test.results are set, and everyone else gets the winning subject. Opens flatter whichever variant reached more Apple Mail users (why), so choose clicks when the email has a link worth clicking.

Test sends

POST /v1/broadcasts/{id}/test with { "to": ["me@acme.example"] } sends the current content to up to five addresses, each as its own email with [Test] before the subject. Test copies are not part of the broadcast: they are not counted and do not change its status. An address that is a contact gets its own variables and working links; any other address gets links that open the recipient pages without changing anything.

Progress and analytics

GET /v1/broadcasts/{id} carries stats:

Field What it counts
recipients Contacts in the snapshot.
sent, pending, skipped, failed Recipients whose email was created, who are still waiting, who were left out at send time, or whose email could not be created.
delivered, opened, clicked, bounced, complained The broadcast's emails with at least one such event (each email once).
unsubscribed Recipients who unsubscribed from marketing mail, or from the broadcast's topic, after their email went out.

The counts follow the broadcast's emails as their events arrive. Once a broadcast has finished, a copy of its counts is kept with it, so they survive the recipient list's 13-month retention.

GET /v1/broadcasts/{id}/recipients lists the snapshot, newest contact first, with each recipient's status, email_id and sent_at. Filter it by status, by part of the address (email, any case), or by what happened to the email with event:

event Recipients Adds
sent, delivered, complained whose email had that event
opened whose email was opened count, opens (once per day)
clicked who clicked a link count and clicked_links (url, clicks), clicks once per link and day
bounced whose email bounced; bounce_type narrows it to permanent, transient or undetermined bounce_type
unsubscribed who unsubscribed after it went out, as stats.unsubscribed counts
suppressed skipped at send time, or refused at delivery, because the address was suppressed

GET /v1/broadcasts/{id}/links lists the links recipients clicked, most clicked first: url, clicks (each recipient once per link and day) and unique_clicks (recipients). Each row's id is a cursor for after and before; it names no object.

Test keys

A test key's broadcasts send test-mode emails, which appear in the sandbox. Test keys see only test-mode broadcasts and live keys only live ones; the console shows both, marked.

Updated