Skip to main content

Endpoints

GET    /v1/email-blasts
POST   /v1/email-blasts
GET    /v1/email-blasts/{blastId}
PATCH  /v1/email-blasts/{blastId}
DELETE /v1/email-blasts/{blastId}
PUT    /v1/email-blasts/{blastId}/audience
PUT    /v1/email-blasts/{blastId}/email
POST   /v1/email-blasts/{blastId}/schedule
POST   /v1/email-blasts/{blastId}/send-now
POST   /v1/email-blasts/{blastId}/unschedule
POST   /v1/email-blasts/{blastId}/archive
GET    /v1/email-blasts/{blastId}/contacts
POST   /v1/email-blasts/{blastId}/contacts/export
GET    /v1/email-blasts/{blastId}/contacts/export/{exportId}
A blast is a one-time send of an email to an audience. The typical workflow is:
  1. Create a draft blast
  2. Attach an audience with PUT .../audience
  3. Attach an email with PUT .../email
  4. Schedule it or send it now
Blast status moves through draftscheduledsendingsent, with failed, cancelled, and archived as side exits. The audience and email are only editable while the blast is a draft.

Create a Blast

POST /v1/email-blasts
FieldTypeRequiredNotes
namestringYesInternal display name, 1-200 characters
descriptionstring | nullNoMaximum 1000 characters
Returns 201 Created with the blast object.

Set the Audience

PUT /v1/email-blasts/{blastId}/audience
Reference an existing contact list, or create a new list and attach it in one call. This replaces any previously attached audience. Returns 200 OK with the updated blast object.

Existing List

FieldTypeRequiredNotes
sourcestringYesexisting
list_iduuidYesAn existing static or dynamic contact list

Inline Static List

FieldTypeRequiredNotes
sourcestringYesinline
kindstringYesstatic
namestringNo1-255 characters. Defaults to "<blast name> (campaign <id>)"
descriptionstring | nullNoMaximum 1000 characters
contact_idsuuid[]YesContacts to seed the list with, maximum 500 per request

Inline Dynamic List

FieldTypeRequiredNotes
sourcestringYesinline
kindstringYesdynamic
namestringNo1-255 characters
descriptionstring | nullNoMaximum 1000 characters
rulesobjectYesSegment rule tree — membership is computed continuously

Audience Rules

Dynamic audiences are defined by a rule tree of groups and conditions. Rule objects use camelCase keys, unlike the rest of the public API — they pass through to the segment engine verbatim.
{
  "kind": "group",
  "operator": "and",
  "conditions": [
    {"kind": "condition", "source": "contact", "field": "state", "operator": "equals", "value": "CA"},
    {"kind": "condition", "source": "list_membership", "listId": "11111111-1111-4111-8111-111111111111", "operator": "is_member"}
  ]
}
Each condition reads one source:
SourceSelectorOperators
contactfield — a standard contact field key in camelCase (e.g. leadStatus, tentedScore, createdAt)Per field type: strings take equals, not_equals, contains, not_contains, starts_with, ends_with; numbers and dates take equals, not_equals, greater_than(_or_equal), less_than(_or_equal); booleans take is_true, is_false. All types take is_empty / is_not_empty
custom_fieldfieldDefinitionId — an active custom field definition IDSame value operators as contact
activityactivityType (e.g. form_submission), optional timeWindow ({"amount": 30, "unit": "day"})has_activity, has_no_activity
list_membershiplistId — a static listis_member, is_not_member
Groups combine children with operator: "and" (default) or "or" and can nest up to 5 levels deep, with at most 100 nodes per tree. Shorthand operator aliases (eq, neq, gt, gte, lt, lte, member_of, occurred, …) are accepted. email_step conditions are not allowed in audiences — they only work inside flow condition steps.

Set the Email

PUT /v1/email-blasts/{blastId}/email
Reference an existing email with {"source": "existing", "email_id": "..."}, or create one inline:
FieldTypeRequiredNotes
sourcestringYesinline
namestringNoDefaults to "<blast name> Email"
subjectstringNo1-998 characters. Must be set before the blast can send
template_iduuidNoSeed content and default headers from an approved template. Mutually exclusive with html
htmlstringNoSeed content from your own HTML, up to 2 MB. Mutually exclusive with template_id
preview_textstringNoMaximum 200 characters
from_namestringNoMaximum 120 characters. Must be set before the blast can send
from_addressstringNoMust be set before the blast can send; domain must be verified
reply_to_emailstringNoMust be set before the blast can send
Omit both template_id and html for a blank branded scaffold. Returns 200 OK with the updated blast object.
The attached email must be approved before the blast can be scheduled or sent. Inline emails start as drafts — take the email.email_id from the response and approve it via POST /v1/emails/{emailId}/approve.

Approval Readiness

GET /v1/email-blasts/{blastId} on a draft includes an approval_readiness object listing what still blocks sending:
{
  "approval_readiness": {
    "ready": false,
    "missing": ["email_approval", "schedule"],
    "warnings": ["empty_static_audience"]
  }
}
Scheduling or sending a blast that is not ready returns 400 Bad Request with error_code: "campaign_not_ready" and the same approval_readiness details.

Schedule a Blast

POST /v1/email-blasts/{blastId}/schedule
FieldTypeRequiredNotes
scheduled_atstringYesISO-8601 datetime with timezone offset, e.g. 2026-07-10T09:00:00-07:00. Must be in the future
operationalbooleanNoDefaults to false — see below
Returns 200 OK with the blast in scheduled status. Use POST .../unschedule to cancel a scheduled send and return the blast to draft.
Setting operational: true marks the send as transactional: unsubscribed contacts are not suppressed and the unsubscribe footer is skipped. Only use it for non-marketing mail such as receipts and service notices.

Send Now

POST /v1/email-blasts/{blastId}/send-now
Accepts the same optional operational flag and starts the send immediately through the async pipeline. Returns 202 Accepted with the blast in sending status; poll GET /v1/email-blasts/{blastId} for progress counters.

Blast Object

200 OK
{
  "blast_id": "e6a1e6de-6a2e-4a3a-9a01-3f1c2b4d5e6f",
  "type": "email_blast",
  "name": "July launch",
  "description": null,
  "status": "sent",
  "audience_list_id": "11111111-1111-4111-8111-111111111111",
  "audience_source": "existing",
  "audience": {
    "list_id": "11111111-1111-4111-8111-111111111111",
    "source": "existing",
    "name": "Newsletter subscribers",
    "kind": "static",
    "member_count": 1250
  },
  "email_id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1",
  "email_source": "existing",
  "email": {
    "email_id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1",
    "source": "existing",
    "name": "July launch email",
    "status": "approved",
    "subject": "The July release is here",
    "current_version": 3,
    "approved_version": 3
  },
  "operational": false,
  "scheduled_at": "2026-07-10T16:00:00.000Z",
  "approved_at": "2026-07-08T09:12:00.000Z",
  "approved_email_version": 3,
  "sent_at": "2026-07-10T16:04:12.000Z",
  "send_mode": "scheduled",
  "details": {
    "blast_recipient_count": 1250,
    "blast_sent_count": 1248,
    "blast_failed_count": 0,
    "blast_blocked_count": 2,
    "blast_qualified_count": 1250,
    "blast_audience_truncated": false,
    "blast_delivered_count": 1240,
    "blast_opened_count": 611,
    "blast_clicked_count": 187,
    "blast_bounced_count": 8,
    "blast_spam_count": 0,
    "blast_unsubscribed_count": 3
  },
  "created_at": "2026-07-08T09:00:00.000Z",
  "updated_at": "2026-07-10T16:20:00.000Z"
}

List Blasts

GET /v1/email-blasts
ParameterTypeDefaultNotes
statusstringanyany, draft, scheduled, sending, sent, failed, cancelled, or archived
archivedstringexcludeexclude, include, or only
sort_bystringupdated_atupdated_at, created_at, scheduled_at, or name
sort_orderstringdescasc or desc
pagenumber11-based page number
limitnumber25Page size, 1-100
searchstringCase-insensitive name search
Responses contain blasts and a pagination object with page, limit, total, and totalPages.

Recipients and Engagement

GET /v1/email-blasts/{blastId}/contacts
Page through a blast’s contacts by engagement category: recipients (everyone prepared for the send, the default), sent, delivered, opened, clicked, bounced, unsubscribed, or spam. Also accepts page, limit, search, sort (engagement *_at timestamps sort contacts without that event last), and order. Responses contain contacts and a pagination object:
{
  "contacts": [
    {
      "recipient_id": "01JZ9J8Q2K7X4W1R5T3V6Y0ZBM",
      "contact_id": "22222222-2222-4222-8222-222222222222",
      "display_name": "Jane Doe",
      "email": "jane@example.com",
      "contact_deleted": false,
      "sent_at": "2026-07-10T16:04:12.000Z",
      "delivered_at": "2026-07-10T16:04:15.000Z",
      "opened_at": "2026-07-10T17:22:03.000Z",
      "clicked_at": null,
      "clicked_url": null,
      "bounced_at": null,
      "unsubscribed_at": null,
      "complained_at": null
    }
  ],
  "pagination": {"page": 1, "limit": 25, "total": 611, "totalPages": 25}
}

Export to CSV

POST /v1/email-blasts/{blastId}/contacts/export?category=opened
GET  /v1/email-blasts/{blastId}/contacts/export/{exportId}
Starting an export returns 202 Accepted with an export job. Poll the job until status is completed, then fetch the file from its download_url:
{
  "export": {
    "export_id": "01JZ9H2M7Q3W8R5T1V6X0YKZ4B",
    "status": "completed",
    "file_name": "july-launch-opened.csv",
    "row_count": 611,
    "error_message": null,
    "download_url": "https://...",
    "created_at": "2026-07-11T10:00:00.000Z",
    "updated_at": "2026-07-11T10:00:09.000Z",
    "completed_at": "2026-07-11T10:00:09.000Z"
  }
}

Rename, Archive, Delete

  • PATCH /v1/email-blasts/{blastId} updates name and/or description.
  • POST /v1/email-blasts/{blastId}/archive archives a blast that is not mid-send.
  • DELETE /v1/email-blasts/{blastId} deletes it and returns {"blast_id": "...", "deleted": true}. Scheduled blasts must be unscheduled first, and archived blasts cannot be deleted.

Common Errors

StatusCause
400 Bad RequestInvalid JSON body or field validation failure
400 Bad RequestThe ID belongs to a triggered flow, not a blast (campaign_not_blast)
400 Bad RequestBlast is not ready to schedule or send (campaign_not_ready, with approval_readiness details)
400 Bad Requestscheduled_at is in the past (campaign_schedule_in_past)
401 UnauthorizedMissing or invalid bearer token
404 Not FoundBlast does not exist in the workspace (campaign_not_found)
409 ConflictAudience or email edited outside draft status (campaign_not_editable)
409 ConflictInvalid lifecycle transition, e.g. unscheduling a blast that is not scheduled (campaign_invalid_state_transition)
409 ConflictDeleting a scheduled or archived blast (campaign_not_deletable)

Next: Manage Triggered Flows

Automate multi-step email journeys that enroll contacts on triggers.