v1

Unitpost API Documentation

Guides to get you sending, plus a complete reference for every API endpoint — generated live from the spec, so it never drifts.

Base URL: https://www.unitpost.com/api/v1

Quickstart

Task-focused walkthroughs. Code samples follow the language you pick in the API reference below.

Install the SDK

Add the official Node, Python, or Ruby SDK — or call the API directly.

Open this guide on its own page

You can integrate in whichever way fits your stack. The official SDKs (all published as `unitpost`) wrap the same REST API with idempotent retries, typed responses, and a `{ data, error }` result shape — or skip the dependency entirely and call the HTTP API directly with cURL or your language's HTTP client.

1. Add the SDK

Pick your language tab. The API tab needs nothing installed — any HTTP client works.

Bash
# No package to install — call the REST API with any HTTP client.
# Keep your key in an environment variable:
export UNITPOST_API_KEY="pk_live_..."

2. Initialize the client

Construct a client once and reuse it. Each SDK reads UNITPOST_API_KEY from the environment by default, or you can pass the key explicitly. Never hard-code a key in source you commit.

cURL
# Every request carries your key as a Bearer token plus a User-Agent.
curl https://www.unitpost.com/api/v1/email \
  -H "Authorization: Bearer $UNITPOST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "User-Agent: my-app/1.0"

Keys live in the environment

Store your API key in an environment variable (UNITPOST_API_KEY) or your secrets manager — not in committed source. Scope it to Sending if the integration only sends mail. See Quickstart for creating and scoping keys.

That's it — you're ready to send. Head to the Quickstart to make your first request, or jump straight to Templates & variables.

Quickstart

Create a key, verify a domain, and send your first email.

Open this guide on its own page

You can send your first email in minutes: create an API key, verify a domain, and make a POST request.

1. Create an API key

Go to API Keys. Choose a preset — Full, Sending, or Read only — or narrow it further. Copy the key when shown.

Scope keys tightly

A server that only sends mail should use a Sending key. Keys can never manage other keys, team, or billing.

2. Verify a sending domain

Add your domain under Domains and publish the generated DNS records. Verification runs automatically.

No domain yet?

While DNS propagates you can still send test emails to your own address from the dashboard onboarding flow — no verified domain required. Test sends confirm the pipeline works; live API sends need a verified domain.

3. Send

Send an email in one call — a from address on your verified domain, a recipient, a subject, and either inline HTML or a template ID. The from accepts a bare address or a display name (`"Acme <you@yourdomain.com>"`).

cURL
curl https://www.unitpost.com/api/v1/email \
  -H "Authorization: Bearer $UNITPOST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "User-Agent: my-app/1.0" \
  -d '{
    "from": "Acme <you@yourdomain.com>",
    "to": "customer@acme.com",
    "subject": "Welcome aboard",
    "html": "<p>Thanks for signing up!</p>"
  }'

The response includes the new email's id. Use GET /api/v1/email/{id} (or `unitpost.email.get(id)`) to read its current status and lifecycle events, or watch it live on the Emails page in the dashboard.

Templates & variables

Reference a saved template and pass per-recipient data.

Open this guide on its own page

Rather than inlining HTML on every send, save a template once and reference it by ID. Pass a data object and any {{variable}} in the template is substituted at send time.

cURL
curl https://www.unitpost.com/api/v1/email \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "you@yourdomain.com",
    "to": "customer@acme.com",
    "template": { "id": "tmpl_123" },
    "data": { "first_name": "Sam", "plan": "Pro" }
  }'

When you send to a known contact, that contact's custom fields merge into the variables automatically — so a template can reference {{first_name}} without you re-sending it on every request.

Drafts can't be sent

A template must be published before the API will send it. Referencing a draft returns a 422 with code template_not_published. Publish it in the editor first.

Templates are authored in the visual editor or in code mode with the @unitpost/email component library — manage them under Templates. The library — every block, its props, and copy-paste snippets — is documented under the Email section in this sidebar.

Sending domains

Add SPF, DKIM, and DMARC so mail sends from your domain.

Open this guide on its own page

To send from your own domain you publish a few DNS records so mailbox providers trust the mail is really from you. Add the domain under Domains and copy the generated records to your DNS provider.

  • DKIM — a TXT record that lets us cryptographically sign your mail.
  • MAIL FROM — a custom return-path subdomain for SPF alignment.
  • DMARC — a policy record that tells receivers what to do with unaligned mail.

Verification is continuous, not one-shot. A domain is Verified, Degraded, or Unverified, and we keep re-checking the records over time.

Degraded still sends

You can send from a Verified or Degraded domain. Degraded means some records drifted but mail still flows — fix the flagged records to return to Verified. You cannot send from an Unverified domain.

Open & click tracking

Control engagement tracking per send, template, or category.

Open this guide on its own page

With tracking on, we inject a 1×1 open pixel and rewrite links so opens and clicks land in the email's event timeline and feed the engagement rates on your dashboard.

How the default is resolved

Tracking is resolved per send in a clear order, most specific first:

  1. An explicit tracking flag on the request wins.
  2. Otherwise the referenced template's default applies.
  3. Otherwise the category default applies.

Sending a receipt or password reset?

Set tracking to false to deliver a clean, untracked transactional message. Many providers and recipients prefer no pixel on sensitive mail.

Bounces, complaints & suppression

How bad addresses are handled and where to review them.

Open this guide on its own page

When an address hard bounces or complains, we add it to your suppression list. It's enforced on every send (single, batch, and campaign), including CC and BCC recipients.

  • Hard bounce — the address is invalid or rejected permanently; it's suppressed (reason: bounced).
  • Complaint — the recipient marked the mail as spam; it's suppressed (reason: complained).
  • Soft bounce — a temporary failure (full mailbox, greylisting); no suppression, and delivery is retried.

Suppressed recipients are excluded, not failed — surfaced as a "Recipients excluded" event on the timeline. Review the list on the Suppressions tab. You can remove an address there to make it mailable again — but re-sending to bad addresses hurts deliverability.

Suppression is separate from marketing unsubscribe

The suppression list is distinct from a contact's marketing preference. An address can be unsubscribed from marketing but mailable for transactional sends, or suppressed outright. Manage blocks on the Suppressions page; manage preferences on the contact.

Get these as webhooks

Every lifecycle event is available in-app today, and you can also have us POST them to your own endpoint in real time. See the Webhooks guide to register an endpoint and verify the signed payloads.

How it all fits together

The contacts → segments → campaigns flow and the rules that keep it safe.

Unitpost has two ways to send: the API (transactional mail — receipts, password resets, one-to-one messages triggered by your app) and Campaigns (one-to-many marketing sends to an audience). Most of the dashboard exists to make the campaign path safe and repeatable, and to give you visibility into both.

The core flow

Three resources form a pipeline, each building on the one before it: Contacts → Segments → Campaigns.

  1. Contacts are the people you can email — an address plus optional name and custom fields. They carry a marketing subscription state; an unsubscribed or suppressed contact is never mailed.
  2. Segments are named groups of contacts. A campaign sends to exactly one segment, so a segment is the unit of "who receives this send." Only subscribed members are mailable.
  3. Campaigns combine a template (the content, authored on the Templates page) with a segment (the audience) and a verified from-domain, then send now or on a schedule.

Read the chain bottom-up when something won't send

A campaign can only send if its template is publishable, its segment has subscribed members, and its from-domain is verified. When a send is blocked, walk back down the chain — domain → segment members → template — and the offending link is almost always one of those three.

Supporting pieces

  • Domains establish sending identity (DKIM/SPF/DMARC). Nothing sends from an address on an unverified domain.
  • Templates hold reusable content; the Library stores the images and assets templates reference.
  • Emails and Activity are the read side — every individual message and every account event, for debugging and auditing.
  • API Keys and Webhooks are the developer surface: keys authenticate the API, webhooks push event notifications back to you.

Archive, don't delete

Deleting a segment or campaign archives it rather than removing it. Archiving keeps history intact and avoids orphaning references. Archived items are hidden from pickers, can be restored, and are swept ~30 days later once nothing active references them.

Active references block destructive actions

You can't archive a segment that a non-terminal campaign (draft, scheduled, sending) needs — cancel or finish that campaign first. This is a clean, explained stop in both the UI and the API.

Each page has its own guide below. New to the product? Follow the sidebar order: verify a Domain, build a Template, add Contacts, group them into a Segment, then create a Campaign.

API Reference

Every endpoint, generated from the live spec.

Emails

Send, schedule, retrieve, and cancel transactional email. Send one email or a batch of up to 100, track each message's delivery lifecycle, and pull aggregate deliverability stats for your workspace.

List sent emails

GET/api/v1/email

GET /email
curl -X GET 'https://www.unitpost.com/api/v1/email' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "email",
      "id": "id_123",
      "to": [
        "customer@example.com"
      ],
      "from": "you@yourdomain.com",
      "subject": "Welcome to Acme",
      "template_id": "template_123",
      "campaign_id": "campaign_123",
      "status": "scheduled",
      "last_event": "string",
      "scheduled_at": "2026-01-01T00:00:00.000Z",
      "sent_at": "2026-01-01T00:00:00.000Z",
      "delivered_at": "2026-01-01T00:00:00.000Z",
      "opened_at": "2026-01-01T00:00:00.000Z",
      "clicked_at": "2026-01-01T00:00:00.000Z",
      "canceled_at": "2026-01-01T00:00:00.000Z",
      "last_error": "string",
      "created_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of emails your workspace has sent or scheduled. Results are newest-first and cursor-paginated, and each row is a reference to a single email (status and lifecycle timestamps, not the rendered body). Narrow the list with the status, campaign_id, batch_id, and created_after/created_before filters. Use an email's id with Retrieve an email to read its full record, or Cancel or reschedule an email to change a scheduled send. For aggregate deliverability numbers over a window, see Email deliverability stats. Requires the emails:read capability.

Parameters8 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
statusstring · queryFilter by send status.
campaign_idstring · queryOnly emails produced by this campaign's fan-out.
batch_idstring · queryOnly emails sent as part of this batch.
created_afterstring · queryOnly emails created at/after this ISO 8601 time.
created_beforestring · queryOnly emails created at/before this ISO 8601 time.
Response · 200 fields17 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
torequiredstring[]
fromrequiredstring
subjectrequiredstring
template_idrequiredobject
campaign_idrequiredobject
statusrequiredenumscheduled | queued | sent | delivered | bounced | complained | canceled | failed
last_eventrequiredstring
scheduled_atrequiredobject
sent_atrequiredobject
delivered_atrequiredobject
opened_atrequiredobject
clicked_atrequiredobject
canceled_atrequiredobject
last_errorrequiredobject
created_atrequiredstring
200401403422

Send or schedule an email

POST/api/v1/email

POST /email
curl -X POST 'https://www.unitpost.com/api/v1/email' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "from": "you@yourdomain.com",
    "to": "customer@example.com",
    "subject": "Welcome to Acme",
    "html": "<h1>Hello</h1>"
  }'
Response · 200
{
  "id": "id_123",
  "status": "queued",
  "scheduled_at": "2026-01-01T00:00:00.000Z"
}

Send a single transactional email — now, or at a future time by passing scheduled_at. Provide the content inline as html/text, or reference a saved template with template (create one with Create a template). Pass an Idempotency-Key header to make retries safe: the same key replays the original result instead of sending twice. The response returns the new email's id — read its delivery status back with Retrieve an email, change a scheduled send with Cancel or reschedule an email, or send many at once with Send a batch of emails. Requires the emails:send capability.

Request body16 fields
FieldTypeDescription
fromrequiredstringSender address on one of your verified domains. Accepts a bare address ("hi@acme.com") or a display name ("Acme <hi@acme.com>") — the name is what inbox clients show next to the message.
torequiredobject
subjectstring
ccobject
bccobject
reply_toobject
in_reply_tostring
headersobject
tagsobject[]
templateobject
htmlstring
textstring
attachmentsobject[]
open_trackingboolean
click_trackingboolean
scheduled_atstring
Response · 200 fields3 fields
FieldTypeDescription
idrequiredstring
statusrequiredenumqueued | scheduled
scheduled_atrequiredstring
200401403404409422429

Send a batch of emails

POST/api/v1/email/batch

POST /email/batch
curl -X POST 'https://www.unitpost.com/api/v1/email/batch' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "emails": [
      {
        "from": "you@yourdomain.com",
        "to": "a@example.com",
        "subject": "Hi A",
        "html": "<p>Hi A</p>"
      },
      {
        "from": "you@yourdomain.com",
        "to": "b@example.com",
        "subject": "Hi B",
        "html": "<p>Hi B</p>"
      }
    ]
  }'
Response · 200
{
  "object": "list",
  "batch_id": "batch_123",
  "status": "queued",
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "data": [
    {
      "id": "id_123"
    }
  ],
  "failed": [
    {
      "index": 0,
      "error": "string"
    }
  ]
}

Send up to 100 transactional emails in a single request. Validation is all-or-nothing: if any item is invalid the whole batch is rejected and nothing is queued, so you never end up with a partial send. Pass an Idempotency-Key header to make retries safe — the same key replays the original result instead of re-sending. The response returns a batch_id; track the batch's per-status rollup with Retrieve a batch and stop the rest of it with Cancel a batch. To send one email, use Send or schedule an email. Requires the emails:send capability.

Response · 200 fields1 field
FieldTypeDescription
idrequiredstring
200401403409422429

Retrieve a batch

GET/api/v1/email/batches/{id}

GET /email/batches/{id}
curl -X GET 'https://www.unitpost.com/api/v1/email/batches/email_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "batch",
  "id": "id_123",
  "count": 1,
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "created_at": "2026-01-01T00:00:00.000Z",
  "status_counts": {},
  "cancelable": 0
}

Retrieve a batch and its live, per-status rollup (how many of its messages are queued, sent, delivered, bounced, and so on). The counts update as the batch progresses. Create a batch with Send a batch of emails, or stop its remaining messages with Cancel a batch. Requires the emails:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields7 fields
FieldTypeDescription
objectrequiredenumbatch
idrequiredstring
countrequiredinteger
scheduled_atrequiredstring
created_atrequiredstring
status_countsrequiredobjectLive child counts keyed by status (e.g. { delivered: 98, failed: 2 }).
cancelablerequiredinteger
200401403404

Cancel a batch

POST/api/v1/email/batches/{id}/cancel

POST /email/batches/{id}/cancel
curl -X POST 'https://www.unitpost.com/api/v1/email/batches/email_123/cancel' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "batch",
  "id": "id_123",
  "count": 1,
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "created_at": "2026-01-01T00:00:00.000Z",
  "status_counts": {},
  "cancelable": 0
}

Cancel every still-cancelable message in a batch in one call. Messages that have already been sent are left untouched — only queued or scheduled ones are stopped. Check what remains cancelable first with Retrieve a batch. Requires the emails:send capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields7 fields
FieldTypeDescription
objectrequiredenumbatch
idrequiredstring
countrequiredinteger
scheduled_atrequiredstring
created_atrequiredstring
status_countsrequiredobjectLive child counts keyed by status (e.g. { delivered: 98, failed: 2 }).
cancelablerequiredinteger
200401403404409

Retrieve an email

GET/api/v1/email/{id}

GET /email/{id}
curl -X GET 'https://www.unitpost.com/api/v1/email/email_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "email",
  "id": "id_123",
  "to": [
    "customer@example.com"
  ],
  "from": "you@yourdomain.com",
  "subject": "Welcome to Acme",
  "template_id": "template_123",
  "campaign_id": "campaign_123",
  "status": "scheduled",
  "last_event": "string",
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "sent_at": "2026-01-01T00:00:00.000Z",
  "delivered_at": "2026-01-01T00:00:00.000Z",
  "opened_at": "2026-01-01T00:00:00.000Z",
  "clicked_at": "2026-01-01T00:00:00.000Z",
  "canceled_at": "2026-01-01T00:00:00.000Z",
  "last_error": "string",
  "created_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a single email by id, including its current send status and lifecycle timestamps (created, scheduled, sent, delivered, and so on). Find an id by browsing List sent emails. To change a scheduled or queued email, use Cancel or reschedule an email. Requires the emails:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields17 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
torequiredstring[]
fromrequiredstring
subjectrequiredstring
template_idrequiredobject
campaign_idrequiredobject
statusrequiredenumscheduled | queued | sent | delivered | bounced | complained | canceled | failed
last_eventrequiredstring
scheduled_atrequiredobject
sent_atrequiredobject
delivered_atrequiredobject
opened_atrequiredobject
clicked_atrequiredobject
canceled_atrequiredobject
last_errorrequiredobject
created_atrequiredstring
200401403404

Cancel or reschedule an email

PATCH/api/v1/email/{id}

PATCH /email/{id}
curl -X PATCH 'https://www.unitpost.com/api/v1/email/email_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "scheduled_at": null
  }'
Response · 200
{
  "object": "email",
  "id": "id_123",
  "to": [
    "customer@example.com"
  ],
  "from": "you@yourdomain.com",
  "subject": "Welcome to Acme",
  "template_id": "template_123",
  "campaign_id": "campaign_123",
  "status": "scheduled",
  "last_event": "string",
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "sent_at": "2026-01-01T00:00:00.000Z",
  "delivered_at": "2026-01-01T00:00:00.000Z",
  "opened_at": "2026-01-01T00:00:00.000Z",
  "clicked_at": "2026-01-01T00:00:00.000Z",
  "canceled_at": "2026-01-01T00:00:00.000Z",
  "last_error": "string",
  "created_at": "2026-01-01T00:00:00.000Z"
}

Cancel a scheduled or queued email, or move it to a new send time. Only emails that haven't been handed off for delivery yet can be changed — once an email is sent this returns 409. Look up the email's current state first with Retrieve an email. Requires the emails:send capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Request body2 fields
FieldTypeDescription
cancelboolean
scheduled_atstring
Response · 200 fields17 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
torequiredstring[]
fromrequiredstring
subjectrequiredstring
template_idrequiredobject
campaign_idrequiredobject
statusrequiredenumscheduled | queued | sent | delivered | bounced | complained | canceled | failed
last_eventrequiredstring
scheduled_atrequiredobject
sent_atrequiredobject
delivered_atrequiredobject
opened_atrequiredobject
clicked_atrequiredobject
canceled_atrequiredobject
last_errorrequiredobject
created_atrequiredstring
200401403404409422

Email deliverability stats

GET/api/v1/email/stats

GET /email/stats
curl -X GET 'https://www.unitpost.com/api/v1/email/stats' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "windowDays": 0,
  "clampedToRetentionDays": 0,
  "totals": {
    "sent": 0,
    "delivered": 0,
    "opened": 0,
    "clicked": 0,
    "bounced": 0,
    "complained": 0,
    "failed": 0
  },
  "rates": {
    "delivery": 0,
    "open": 0,
    "click": 0,
    "bounce": 0,
    "complaint": 0
  }
}

Retrieve aggregate deliverability and engagement metrics for your workspace over a rolling window. You get totals (sent, delivered, opened, clicked, bounced, complained, failed) alongside the rates that matter — delivery, open, click, bounce, and complaint. Open and click rates count only emails where that tracking was enabled, so turning tracking off for transactional mail won't skew them. Control the window with the days parameter (1–365, default 30). For the individual emails behind these numbers, see List sent emails. Requires the emails:read capability.

Parameters1 field
FieldTypeDescription
daysinteger · queryWindow size in days (1–365). Defaults to 30; values above 365 are rejected.
Response · 200 fields4 fields
FieldTypeDescription
windowDaysrequiredintegerThe rolling window, in days.
clampedToRetentionDaysintegerNon-null when the requested window exceeded the plan's log retention and was clamped to this many days (data past the retention horizon is purged).
totalsrequiredobject
ratesrequiredobjectPercentages (one decimal). delivery/bounce/complaint are vs sent; open/click are vs delivered.
200401403422

Inbound emails

Read email your receiving-enabled domains have received — list messages, retrieve full bodies, and download attachments via short-lived signed URLs.

List received emails

GET/api/v1/email/received

GET /email/received
curl -X GET 'https://www.unitpost.com/api/v1/email/received' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "received_email",
      "id": "id_123",
      "from": "you@yourdomain.com",
      "from_name": "string",
      "to": [
        "customer@example.com"
      ],
      "cc": [
        "string"
      ],
      "bcc": [
        "string"
      ],
      "message_id": "message_123",
      "subject": "Welcome to Acme",
      "spam_verdict": "string",
      "virus_verdict": "string",
      "spf_verdict": "string",
      "dkim_verdict": "string",
      "dmarc_verdict": "string",
      "size": 1,
      "received_at": "2026-01-01T00:00:00.000Z",
      "created_at": "2026-01-01T00:00:00.000Z",
      "attachment_count": 1
    }
  ]
}

Retrieve a list of inbound emails received on your receiving-enabled domains, newest-first and cursor-paginated. Rows are summaries (sender, subject, timestamps) without bodies — use a message's id with Retrieve a received email to read the full content and attachment metadata. Requires the emails:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields18 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
fromrequiredstring
from_namerequiredobject
torequiredstring[]
ccrequiredstring[]
bccrequiredstring[]
message_idrequiredobject
subjectrequiredobject
spam_verdictrequiredobject
virus_verdictrequiredobject
spf_verdictrequiredobject
dkim_verdictrequiredobject
dmarc_verdictrequiredobject
sizerequirednumber
received_atrequiredstring
created_atrequiredstring
attachment_countrequirednumber
200401403422

Retrieve a received email

GET/api/v1/email/received/{id}

GET /email/received/{id}
curl -X GET 'https://www.unitpost.com/api/v1/email/received/email_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "received_email",
  "id": "id_123",
  "from": "you@yourdomain.com",
  "from_name": "string",
  "to": [
    "customer@example.com"
  ],
  "cc": [
    "string"
  ],
  "bcc": [
    "string"
  ],
  "message_id": "message_123",
  "subject": "Welcome to Acme",
  "text": "string",
  "html": "string",
  "spam_verdict": "string",
  "virus_verdict": "string",
  "spf_verdict": "string",
  "dkim_verdict": "string",
  "dmarc_verdict": "string",
  "size": 1,
  "attachments": [
    {
      "object": "received_attachment",
      "id": "id_123",
      "filename": "string",
      "content_type": "string",
      "content_disposition": "string",
      "content_id": "content_123",
      "size": 1
    }
  ],
  "received_at": "2026-01-01T00:00:00.000Z",
  "created_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a full inbound message by id, including its HTML and text bodies plus metadata for every attachment. Attachment bytes aren't inlined — download each one via Get an attachment download URL. Browse your inbound mail with List received emails. Requires the emails:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields20 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
fromrequiredstring
from_namerequiredobject
torequiredstring[]
ccrequiredstring[]
bccrequiredstring[]
message_idrequiredobject
subjectrequiredobject
textrequiredobject
htmlrequiredobject
spam_verdictrequiredobject
virus_verdictrequiredobject
spf_verdictrequiredobject
dkim_verdictrequiredobject
dmarc_verdictrequiredobject
sizerequirednumber
attachmentsrequiredobject[]
received_atrequiredstring
created_atrequiredstring
200401403404

Get an attachment download URL

GET/api/v1/email/received/{id}/attachments/{attachmentId}

GET /email/received/{id}/attachments/{attachmentId}
curl -X GET 'https://www.unitpost.com/api/v1/email/received/email_123/attachments/att_789' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "received_attachment_url",
  "url": "https://example.com",
  "expires_at": "2026-01-01T00:00:00.000Z"
}

Get a short-lived, signed URL for downloading one inbound attachment's bytes. Fetch the URL and download promptly — it expires quickly (the expiry is included in the response). Find attachment ids on the message via Retrieve a received email. Requires the emails:read capability.

Parameters2 fields
FieldTypeDescription
idrequiredstring · path
attachmentIdrequiredstring · path
Response · 200 fields3 fields
FieldTypeDescription
objectrequiredenumreceived_attachment_url
urlrequiredstringA signed, expiring URL to download the attachment bytes.
expires_atrequiredstringISO 8601 expiry of the signed URL (~5 minutes out).
200401403404503

Contacts

Manage the people you email. Create and update contacts one at a time or import thousands in the background, with standard and custom field values that templates can reference as variables.

List contacts

GET/api/v1/contacts

GET /contacts
curl -X GET 'https://www.unitpost.com/api/v1/contacts' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "contact",
      "id": "id_123",
      "email": "customer@example.com",
      "first_name": "string",
      "last_name": "string",
      "unsubscribed": false,
      "unsubscribed_at": "2026-01-01T00:00:00.000Z",
      "unsubscribe_reason": "USER",
      "custom_fields": {},
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of the contacts in your workspace, newest-first and cursor-paginated. Each row includes the contact's core fields and any custom field values. Use a contact's id (or email) with Retrieve a contact for the full record, add contacts with Create a contact, or bring a list in bulk with Start an async contact import. Requires the contacts:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields11 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
emailrequiredobject
first_namerequiredobject
last_namerequiredobject
unsubscribedrequiredboolean
unsubscribed_atrequiredobject
unsubscribe_reasonrequiredobject
custom_fieldsrequiredobject
created_atrequiredstring
updated_atrequiredstring
200401422

Create a contact

POST/api/v1/contacts

POST /contacts
curl -X POST 'https://www.unitpost.com/api/v1/contacts' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "customer@example.com",
    "first_name": "Ada",
    "last_name": "Lovelace"
  }'
Response · 201
{
  "object": "contact",
  "id": "id_123",
  "email": "customer@example.com",
  "first_name": "string",
  "last_name": "string",
  "unsubscribed": false,
  "unsubscribed_at": "2026-01-01T00:00:00.000Z",
  "unsubscribe_reason": "USER",
  "custom_fields": {},
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Add a single contact to your workspace. Set an email plus any standard or custom field values; custom fields must already exist (define them with Create a custom contact field). Emails are unique per workspace, so creating one that already exists returns 409 — use Update a contact to change an existing contact, or Start an async contact import to add many at once. Requires the contacts:write capability.

Request body5 fields
FieldTypeDescription
emailrequiredstring
first_namestring
last_namestring
unsubscribedboolean
custom_fieldsobject
Response · 201 fields11 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
emailrequiredobject
first_namerequiredobject
last_namerequiredobject
unsubscribedrequiredboolean
unsubscribed_atrequiredobject
unsubscribe_reasonrequiredobject
custom_fieldsrequiredobject
created_atrequiredstring
updated_atrequiredstring
201401409422

Retrieve a contact

GET/api/v1/contacts/{id}

GET /contacts/{id}
curl -X GET 'https://www.unitpost.com/api/v1/contacts/con_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "contact",
  "id": "id_123",
  "email": "customer@example.com",
  "first_name": "string",
  "last_name": "string",
  "unsubscribed": false,
  "unsubscribed_at": "2026-01-01T00:00:00.000Z",
  "unsubscribe_reason": "USER",
  "custom_fields": {},
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a single contact and all of its field values. The {id} path segment accepts either the contact's id or its email address, so you can look a contact up without storing our id. Change it with Update a contact, or see which subscription topics it's opted into with List a contact's topic subscriptions. Requires the contacts:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields11 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
emailrequiredobject
first_namerequiredobject
last_namerequiredobject
unsubscribedrequiredboolean
unsubscribed_atrequiredobject
unsubscribe_reasonrequiredobject
custom_fieldsrequiredobject
created_atrequiredstring
updated_atrequiredstring
200401404

Update a contact

PATCH/api/v1/contacts/{id}

PATCH /contacts/{id}
curl -X PATCH 'https://www.unitpost.com/api/v1/contacts/con_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "unsubscribed": true
  }'
Response · 200
{
  "object": "contact",
  "id": "id_123",
  "email": "customer@example.com",
  "first_name": "string",
  "last_name": "string",
  "unsubscribed": false,
  "unsubscribed_at": "2026-01-01T00:00:00.000Z",
  "unsubscribe_reason": "USER",
  "custom_fields": {},
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Update a contact's standard or custom field values. Only the fields you send are changed; omitted fields are left as-is. The {id} accepts a contact id or email. To manage a contact's subscription preferences instead, use Set a contact's topic preference. Requires the contacts:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Request body4 fields
FieldTypeDescription
first_nameobject
last_nameobject
unsubscribedboolean
custom_fieldsobject
Response · 200 fields11 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
emailrequiredobject
first_namerequiredobject
last_namerequiredobject
unsubscribedrequiredboolean
unsubscribed_atrequiredobject
unsubscribe_reasonrequiredobject
custom_fieldsrequiredobject
created_atrequiredstring
updated_atrequiredstring
200401404422

Delete a contact

DELETE/api/v1/contacts/{id}

DELETE /contacts/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/contacts/con_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Permanently delete a contact and its field values. This also removes the contact from every segment it belonged to. To stop mailing someone without deleting them, unsubscribe them from a topic with Set a contact's topic preference or add them to your suppression list instead. Requires the contacts:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
204401404

List contact imports

GET/api/v1/contacts/imports

GET /contacts/imports
curl -X GET 'https://www.unitpost.com/api/v1/contacts/imports' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "contact_import",
      "id": "id_123",
      "status": "pending",
      "total": 1,
      "imported": 0,
      "skipped": 0,
      "failed": 0,
      "invalid": [
        {
          "row": 0,
          "reason": "string"
        }
      ],
      "error": "string",
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of your bulk contact-import jobs, newest-first and cursor-paginated. Each row is a reference to one import and its current progress. Start a new one with Start an async contact import, or drill into a single job with Retrieve a contact import. Requires the contacts:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields11 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
statusrequiredenumpending | processing | completed | failed
totalrequirednumber
importedrequirednumber
skippedrequirednumber
failedrequirednumber
invalidrequiredobject[]
errorrequiredobject
created_atrequiredstring
updated_atrequiredstring
200401422

Start an async contact import

POST/api/v1/contacts/imports

POST /contacts/imports
curl -X POST 'https://www.unitpost.com/api/v1/contacts/imports' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ }'
Response · 202
{
  "object": "contact_import",
  "id": "id_123",
  "status": "pending",
  "total": 1,
  "imported": 0,
  "skipped": 0,
  "failed": 0,
  "invalid": [
    {
      "row": 0,
      "reason": "string"
    }
  ],
  "error": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Import a batch of contacts (up to 5,000 per request) in the background. The call returns immediately with an import job you poll for progress and per-row results — new contacts are created and existing ones (matched by email) are updated. Track it with Retrieve a contact import, or see all your imports via List contact imports. To add a single contact synchronously, use Create a contact. Requires the contacts:write capability.

Request body1 field
FieldTypeDescription
contactsrequiredobject[]
Response · 202 fields11 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
statusrequiredenumpending | processing | completed | failed
totalrequirednumber
importedrequirednumber
skippedrequirednumber
failedrequirednumber
invalidrequiredobject[]
errorrequiredobject
created_atrequiredstring
updated_atrequiredstring
202401422

Retrieve a contact import

GET/api/v1/contacts/imports/{id}

GET /contacts/imports/{id}
curl -X GET 'https://www.unitpost.com/api/v1/contacts/imports/con_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "contact_import",
  "id": "id_123",
  "status": "pending",
  "total": 1,
  "imported": 0,
  "skipped": 0,
  "failed": 0,
  "invalid": [
    {
      "row": 0,
      "reason": "string"
    }
  ],
  "error": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a bulk import job by id to check its status and per-row results — how many contacts were created, updated, or skipped, and why any rows failed. Poll this while an import runs. Start one with Start an async contact import. Requires the contacts:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields11 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
statusrequiredenumpending | processing | completed | failed
totalrequirednumber
importedrequirednumber
skippedrequirednumber
failedrequirednumber
invalidrequiredobject[]
errorrequiredobject
created_atrequiredstring
updated_atrequiredstring
200401404

Contact fields

Define the typed custom attributes (text, number, date, …) your contacts carry — usable in templates as per-recipient variables.

List custom contact fields

GET/api/v1/contact-fields

GET /contact-fields
curl -X GET 'https://www.unitpost.com/api/v1/contact-fields' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "contact_field",
      "id": "id_123",
      "key": "string",
      "label": "string",
      "type": "text",
      "default_value": "string",
      "position": 0,
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of the custom contact fields defined in your workspace, cursor-paginated. These are the typed attributes (text, number, date, boolean, and so on) you can set on any contact and reference as template variables. Define a new one with Create a custom contact field. Requires the contacts:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
keyrequiredstring
labelrequiredstring
typerequiredenumtext | number | boolean | date
default_valuerequiredobject
positionrequirednumber
created_atrequiredstring
updated_atrequiredstring
200401422

Create a custom contact field

POST/api/v1/contact-fields

POST /contact-fields
curl -X POST 'https://www.unitpost.com/api/v1/contact-fields' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Plan",
    "type": "string"
  }'
Response · 201
{
  "object": "contact_field",
  "id": "id_123",
  "key": "string",
  "label": "string",
  "type": "text",
  "default_value": "string",
  "position": 0,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Define a new typed custom field that can then be set on any contact and used as a template variable (e.g. plan, signup_date). The key and type are fixed once created — rename the key later with Rename a custom field's key, or change the label with Update a custom contact field. See existing fields via List custom contact fields. Requires the contacts:write capability.

Request body4 fields
FieldTypeDescription
keyrequiredstring
labelrequiredstring
typeenumtext | number | boolean | date
default_valueobject
Response · 201 fields9 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
keyrequiredstring
labelrequiredstring
typerequiredenumtext | number | boolean | date
default_valuerequiredobject
positionrequirednumber
created_atrequiredstring
updated_atrequiredstring
201401409422

Retrieve a custom contact field

GET/api/v1/contact-fields/{id}

GET /contact-fields/{id}
curl -X GET 'https://www.unitpost.com/api/v1/contact-fields/cf_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "contact_field",
  "id": "id_123",
  "key": "string",
  "label": "string",
  "type": "text",
  "default_value": "string",
  "position": 0,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a single custom contact field by id, including its key, type, and label. Update its label with Update a custom contact field, or rename the key with Rename a custom field's key. Requires the contacts:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
keyrequiredstring
labelrequiredstring
typerequiredenumtext | number | boolean | date
default_valuerequiredobject
positionrequirednumber
created_atrequiredstring
updated_atrequiredstring
200401404

Update a custom contact field

PATCH/api/v1/contact-fields/{id}

PATCH /contact-fields/{id}
curl -X PATCH 'https://www.unitpost.com/api/v1/contact-fields/cf_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ }'
Response · 200
{
  "object": "contact_field",
  "id": "id_123",
  "key": "string",
  "label": "string",
  "type": "text",
  "default_value": "string",
  "position": 0,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Update a custom field's editable metadata, such as its display label. The key and type are immutable here — to change the key (and migrate every contact's stored value) use Rename a custom field's key instead. Requires the contacts:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Request body4 fields
FieldTypeDescription
labelstring
typeenumtext | number | boolean | date
positioninteger
default_valueobject
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
keyrequiredstring
labelrequiredstring
typerequiredenumtext | number | boolean | date
default_valuerequiredobject
positionrequirednumber
created_atrequiredstring
updated_atrequiredstring
200401404422

Delete a custom contact field

DELETE/api/v1/contact-fields/{id}

DELETE /contact-fields/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/contact-fields/cf_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Permanently delete a custom field definition and strip its stored value from every contact. Any template variable that referenced it will resolve to empty. This can't be undone — recreate the field with Create a custom contact field if you need it back. Requires the contacts:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
204401404

Rename a custom field's key

POST/api/v1/contact-fields/{id}/rename

POST /contact-fields/{id}/rename
curl -X POST 'https://www.unitpost.com/api/v1/contact-fields/cf_123/rename' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Subscription plan"
  }'
Response · 200
{
  "object": "contact_field",
  "id": "id_123",
  "key": "string",
  "label": "string",
  "type": "text",
  "default_value": "string",
  "position": 0,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Change a custom field's key, migrating the stored value on every contact so no data is lost. Because the key is how templates and the API reference the field, update any template variables that used the old key. To change only the display label, use Update a custom contact field. Requires the contacts:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Request body1 field
FieldTypeDescription
keyrequiredstring
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
keyrequiredstring
labelrequiredstring
typerequiredenumtext | number | boolean | date
default_valuerequiredobject
positionrequirednumber
created_atrequiredstring
updated_atrequiredstring
200401404409422

Segments

Named groups of contacts used to target campaigns. Segments decide who a campaign goes to; topics (below) decide what recipients can opt out of.

List segments

GET/api/v1/segments

GET /segments
curl -X GET 'https://www.unitpost.com/api/v1/segments' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "segment",
      "id": "id_123",
      "name": "Example",
      "description": "string",
      "type": "static",
      "filter": "string",
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of the segments in your workspace, cursor-paginated. A segment is a saved group of contacts you can target when sending a campaign. Create one with Create a segment, or list a segment's members with List segment members. Requires the segments:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields8 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
descriptionrequiredobject
typerequiredenumstatic | dynamic
filterrequiredobject
created_atrequiredstring
updated_atrequiredstring
200401422

Create a segment

POST/api/v1/segments

POST /segments
curl -X POST 'https://www.unitpost.com/api/v1/segments' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Active customers"
  }'
Response · 201
{
  "object": "segment",
  "id": "id_123",
  "name": "Example",
  "description": "string",
  "type": "static",
  "filter": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Create a segment — a named group of contacts you can target when sending a campaign. Once created, add contacts with Add a member and target it from Create a campaign. To let recipients opt out of a category of mail instead, see Create a topic. Requires the segments:write capability.

Request body4 fields
FieldTypeDescription
namerequiredstring
descriptionstring
typeenumstatic | dynamic
filterobject
Response · 201 fields8 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
descriptionrequiredobject
typerequiredenumstatic | dynamic
filterrequiredobject
created_atrequiredstring
updated_atrequiredstring
201401422

Retrieve a segment

GET/api/v1/segments/{id}

GET /segments/{id}
curl -X GET 'https://www.unitpost.com/api/v1/segments/seg_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "segment",
  "id": "id_123",
  "name": "Example",
  "description": "string",
  "type": "static",
  "filter": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a single segment by id, including its name and member count. To page through the contacts inside it, use List segment members. Requires the segments:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields8 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
descriptionrequiredobject
typerequiredenumstatic | dynamic
filterrequiredobject
created_atrequiredstring
updated_atrequiredstring
200401404

Update a segment

PATCH/api/v1/segments/{id}

PATCH /segments/{id}
curl -X PATCH 'https://www.unitpost.com/api/v1/segments/seg_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ }'
Response · 200
{
  "object": "segment",
  "id": "id_123",
  "name": "Example",
  "description": "string",
  "type": "static",
  "filter": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Update a segment's editable metadata, such as its name. To change who's in the segment, add or remove members with Add a member and Remove a member. Requires the segments:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Request body3 fields
FieldTypeDescription
namestring
descriptionobject
filterobject
Response · 200 fields8 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
descriptionrequiredobject
typerequiredenumstatic | dynamic
filterrequiredobject
created_atrequiredstring
updated_atrequiredstring
200401404422

Delete a segment

DELETE/api/v1/segments/{id}

DELETE /segments/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/segments/seg_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Delete a segment. The contacts themselves are untouched — only the grouping is removed. This is blocked with 409 while an active campaign still targets the segment; cancel or finish that campaign first (see List campaigns). Requires the segments:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
204401404409

List segment members

GET/api/v1/segments/{id}/contacts

GET /segments/{id}/contacts
curl -X GET 'https://www.unitpost.com/api/v1/segments/seg_123/contacts' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "segment_member",
      "contact_id": "contact_123",
      "subscribed": false,
      "unsubscribed_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve the contacts in a segment, cursor-paginated. Add a contact with Add a member or take one out with Remove a member. Requires the segments:read capability.

Parameters4 fields
FieldTypeDescription
idrequiredstring · path
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields4 fields
FieldTypeDescription
objectrequiredstring
contact_idrequiredstring
subscribedrequiredboolean
unsubscribed_atrequiredobject
200401404422

Add a member

POST/api/v1/segments/{id}/contacts

POST /segments/{id}/contacts
curl -X POST 'https://www.unitpost.com/api/v1/segments/seg_123/contacts' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "contact": "customer@example.com"
  }'
Response · 201
{
  "object": "segment_member",
  "contact_id": "contact_123",
  "subscribed": false,
  "unsubscribed_at": "2026-01-01T00:00:00.000Z"
}

Add a contact to a segment, referenced by contact id or email. The operation is idempotent — adding a contact that's already a member is a no-op and still succeeds. See the current members with List segment members. Requires the segments:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Request body2 fields
FieldTypeDescription
contactrequiredstring
subscribedboolean
Response · 201 fields4 fields
FieldTypeDescription
objectrequiredstring
contact_idrequiredstring
subscribedrequiredboolean
unsubscribed_atrequiredobject
201401404422

Remove a member

DELETE/api/v1/segments/{id}/contacts/{contact}

DELETE /segments/{id}/contacts/{contact}
curl -X DELETE 'https://www.unitpost.com/api/v1/segments/seg_123/contacts/con_456' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Remove a contact from a segment, referenced by contact id or email. The contact itself is not deleted — only its membership. Add it back any time with Add a member. Requires the segments:write capability.

Parameters2 fields
FieldTypeDescription
idrequiredstring · path
contactrequiredstring · pathA contact id or email.
204401404

Topics

Subscription categories (e.g. Promotions) recipients can opt out of without leaving your list, independent of segment membership. Scope a campaign to a topic and opted-out contacts are skipped automatically.

List topics

GET/api/v1/email/topics

GET /email/topics
curl -X GET 'https://www.unitpost.com/api/v1/email/topics' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "topic",
      "id": "id_123",
      "name": "Example",
      "description": "string",
      "default_opt_in": false,
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of your subscription topics, cursor-paginated. A topic is a category of email (e.g. Product updates, Promotions) that recipients can opt out of without leaving your list entirely. Pass ?archived=true to list archived topics instead. Create one with Create a topic, or check a contact's preferences with List a contact's topic subscriptions. Requires the topics:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields7 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
descriptionrequiredobject
default_opt_inrequiredboolean
created_atrequiredstring
updated_atrequiredstring
200401422

Create a topic

POST/api/v1/email/topics

POST /email/topics
curl -X POST 'https://www.unitpost.com/api/v1/email/topics' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Product updates",
    "default_subscribed": true
  }'
Response · 201
{
  "object": "topic",
  "id": "id_123",
  "name": "Example",
  "description": "string",
  "default_opt_in": false,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Create a subscription topic — a category recipients can opt out of on their own (e.g. Promotions) while staying on your list. default_opt_in decides whether contacts with no explicit preference are treated as subscribed. Scope a campaign to a topic so opted-out contacts are skipped automatically via Create a campaign, and set an individual contact's preference with Set a contact's topic preference. Topics differ from segments: a segment decides who a campaign targets, a topic lets recipients opt out. Requires the topics:write capability.

Request body3 fields
FieldTypeDescription
namerequiredstring
descriptionstring
default_opt_inboolean
Response · 201 fields7 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
descriptionrequiredobject
default_opt_inrequiredboolean
created_atrequiredstring
updated_atrequiredstring
201401409422

Retrieve a topic

GET/api/v1/email/topics/{id}

GET /email/topics/{id}
curl -X GET 'https://www.unitpost.com/api/v1/email/topics/top_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "topic",
  "id": "id_123",
  "name": "Example",
  "description": "string",
  "default_opt_in": false,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a single topic by id, including its name and default_opt_in setting. Change it with Update a topic, or list every topic with List topics. Requires the topics:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields7 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
descriptionrequiredobject
default_opt_inrequiredboolean
created_atrequiredstring
updated_atrequiredstring
200401404

Update a topic

PATCH/api/v1/email/topics/{id}

PATCH /email/topics/{id}
curl -X PATCH 'https://www.unitpost.com/api/v1/email/topics/top_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ }'
Response · 200
{
  "object": "topic",
  "id": "id_123",
  "name": "Example",
  "description": "string",
  "default_opt_in": false,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Update a topic's name or default_opt_in behavior, or pass { "archived": true | false } to archive or restore it. Archiving a topic hides it and stops it from being targeted, but is blocked with 409 while an active campaign still targets it. To manage a single contact's preference, use Set a contact's topic preference. Requires the topics:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Request body3 fields
FieldTypeDescription
namestring
descriptionobject
default_opt_inboolean
Response · 200 fields7 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
descriptionrequiredobject
default_opt_inrequiredboolean
created_atrequiredstring
updated_atrequiredstring
200401404409422

Delete a topic

DELETE/api/v1/email/topics/{id}

DELETE /email/topics/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/email/topics/top_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Archive a topic (a soft delete — existing subscription records are preserved so you can restore it later). It's blocked with 409 while an active campaign still targets it. Restore an archived topic by sending { "archived": false } to Update a topic. Requires the topics:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
204401404409

List a contact's topic subscriptions

GET/api/v1/contacts/{id}/topics

GET /contacts/{id}/topics
curl -X GET 'https://www.unitpost.com/api/v1/contacts/top_123/topics' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "contact_topic",
      "topic_id": "topic_123",
      "subscribed": false,
      "status": "subscribed",
      "source": "user"
    }
  ]
}

Retrieve a contact's effective subscription state for every active topic, cursor-paginated. Each row resolves the contact's explicit preference against the topic's default_opt_in, so subscribed is always the answer to "would this contact receive a campaign scoped to this topic?". Change a preference with Set a contact's topic preference. Requires the topics:read capability.

Parameters4 fields
FieldTypeDescription
idrequiredstring · path
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields5 fields
FieldTypeDescription
objectrequiredstring
topic_idrequiredstring
subscribedrequiredboolean
statusrequiredenumsubscribed | unsubscribed
sourcerequiredenumuser | manual | import | api
200401404422

Set a contact's topic preference

POST/api/v1/contacts/{id}/topics

POST /contacts/{id}/topics
curl -X POST 'https://www.unitpost.com/api/v1/contacts/top_123/topics' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "topic_id": "top_123",
    "subscribed": false
  }'
Response · 201
{
  "object": "contact_topic",
  "topic_id": "topic_123",
  "subscribed": false,
  "status": "subscribed",
  "source": "user"
}

Subscribe or unsubscribe a contact for one topic. This records an explicit preference that overrides the topic's default_opt_in, and it's idempotent — setting the same preference twice succeeds. PUT behaves identically. Read the contact's current state with List a contact's topic subscriptions. Requires the topics:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Request body3 fields
FieldTypeDescription
topic_idstring
topicstring
subscribedrequiredboolean
Response · 201 fields5 fields
FieldTypeDescription
objectrequiredstring
topic_idrequiredstring
subscribedrequiredboolean
statusrequiredenumsubscribed | unsubscribed
sourcerequiredenumuser | manual | import | api
201401404422

Set a contact's topic preference (alias of POST)

PUT/api/v1/contacts/{id}/topics

PUT /contacts/{id}/topics
curl -X PUT 'https://www.unitpost.com/api/v1/contacts/top_123/topics' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "topic_id": "top_123",
    "subscribed": false
  }'
Response · 201
{
  "object": "contact_topic",
  "topic_id": "topic_123",
  "subscribed": false,
  "status": "subscribed",
  "source": "user"
}

Identical to Set a contact's topic preference — provided so clients that prefer PUT semantics for an upsert can use it interchangeably. Requires the topics:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Request body3 fields
FieldTypeDescription
topic_idstring
topicstring
subscribedrequiredboolean
Response · 201 fields5 fields
FieldTypeDescription
objectrequiredstring
topic_idrequiredstring
subscribedrequiredboolean
statusrequiredenumsubscribed | unsubscribed
sourcerequiredenumuser | manual | import | api
201401404422

Campaigns

One-to-many sends to a segment or an inline recipient list. Draft, validate, schedule, send, pause, resume, and cancel — with a pre-send report that catches problems before anything goes out.

List campaigns

GET/api/v1/email/campaigns

GET /email/campaigns
curl -X GET 'https://www.unitpost.com/api/v1/email/campaigns' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "campaign",
      "id": "id_123",
      "name": "Example",
      "template_id": "template_123",
      "segment_id": "segment_123",
      "topic_id": "topic_123",
      "from": "you@yourdomain.com",
      "subject": "Welcome to Acme",
      "preview_text": "string",
      "open_tracking": false,
      "click_tracking": false,
      "status": "draft",
      "scheduled_at": "2026-01-01T00:00:00.000Z",
      "variable_fallbacks": {},
      "excluded_contact_ids": [
        "string"
      ],
      "total_recipients": 1,
      "sent_count": 1,
      "failed_count": 1,
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of your campaigns, cursor-paginated. Each row is a reference to one campaign and its current status (draft, scheduled, sending, sent, …). Use a campaign's id with Retrieve a campaign for the full record, or with List sent emails (?campaign_id=) to see the individual emails it produced. Requires the campaigns:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields20 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
template_idrequiredstring
segment_idrequiredstring
topic_idrequiredobject
fromrequiredstring
subjectrequiredobject
preview_textrequiredobject
open_trackingrequiredobject
click_trackingrequiredobject
statusrequiredenumdraft | scheduled | sending | paused | sent | canceled | failed
scheduled_atrequiredobject
variable_fallbacksrequiredobject
excluded_contact_idsrequiredstring[]
total_recipientsrequirednumber
sent_countrequirednumber
failed_countrequirednumber
created_atrequiredstring
updated_atrequiredstring
200401422

Create a campaign

POST/api/v1/email/campaigns

POST /email/campaigns
curl -X POST 'https://www.unitpost.com/api/v1/email/campaigns' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "June newsletter",
    "subject": "What's new",
    "from": "you@yourdomain.com"
  }'
Response · 201
{
  "object": "campaign",
  "id": "id_123",
  "name": "Example",
  "template_id": "template_123",
  "segment_id": "segment_123",
  "topic_id": "topic_123",
  "from": "you@yourdomain.com",
  "subject": "Welcome to Acme",
  "preview_text": "string",
  "open_tracking": false,
  "click_tracking": false,
  "status": "draft",
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "variable_fallbacks": {},
  "excluded_contact_ids": [
    "string"
  ],
  "total_recipients": 1,
  "sent_count": 1,
  "failed_count": 1,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Create a campaign in draft. Target either a saved segment (segment_id, see Create a segment) or an inline recipients list, and optionally scope it to a subscription topic (topic_id) so contacts who opted out of that topic are skipped automatically. Content comes from a template_id or inline html/text. Nothing is sent yet — edit the draft with Update a campaign, check it with Validate a campaign, then launch it with Send a campaign. Requires the campaigns:write capability.

Request body13 fields
FieldTypeDescription
namerequiredstring
template_idrequiredstring
segment_idstring
recipientsstring[]
topic_idstring
fromrequiredstring
subjectstring
preview_textstring
scheduled_atstring
open_trackingobject
click_trackingobject
variable_fallbacksobject
excluded_contact_idsstring[]
Response · 201 fields20 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
template_idrequiredstring
segment_idrequiredstring
topic_idrequiredobject
fromrequiredstring
subjectrequiredobject
preview_textrequiredobject
open_trackingrequiredobject
click_trackingrequiredobject
statusrequiredenumdraft | scheduled | sending | paused | sent | canceled | failed
scheduled_atrequiredobject
variable_fallbacksrequiredobject
excluded_contact_idsrequiredstring[]
total_recipientsrequirednumber
sent_countrequirednumber
failed_countrequirednumber
created_atrequiredstring
updated_atrequiredstring
201401422

Retrieve a campaign

GET/api/v1/email/campaigns/{id}

GET /email/campaigns/{id}
curl -X GET 'https://www.unitpost.com/api/v1/email/campaigns/cmp_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "campaign",
  "id": "id_123",
  "name": "Example",
  "template_id": "template_123",
  "segment_id": "segment_123",
  "topic_id": "topic_123",
  "from": "you@yourdomain.com",
  "subject": "Welcome to Acme",
  "preview_text": "string",
  "open_tracking": false,
  "click_tracking": false,
  "status": "draft",
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "variable_fallbacks": {},
  "excluded_contact_ids": [
    "string"
  ],
  "total_recipients": 1,
  "sent_count": 1,
  "failed_count": 1,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a single campaign by id, including its status, targeting, content references, and schedule. To see the individual emails a sent campaign produced, filter List sent emails by campaign_id; for a pre-send readiness report, use Validate a campaign. Requires the campaigns:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields20 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
template_idrequiredstring
segment_idrequiredstring
topic_idrequiredobject
fromrequiredstring
subjectrequiredobject
preview_textrequiredobject
open_trackingrequiredobject
click_trackingrequiredobject
statusrequiredenumdraft | scheduled | sending | paused | sent | canceled | failed
scheduled_atrequiredobject
variable_fallbacksrequiredobject
excluded_contact_idsrequiredstring[]
total_recipientsrequirednumber
sent_countrequirednumber
failed_countrequirednumber
created_atrequiredstring
updated_atrequiredstring
200401404

Update a campaign

PATCH/api/v1/email/campaigns/{id}

PATCH /email/campaigns/{id}
curl -X PATCH 'https://www.unitpost.com/api/v1/email/campaigns/cmp_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ }'
Response · 200
{
  "object": "campaign",
  "id": "id_123",
  "name": "Example",
  "template_id": "template_123",
  "segment_id": "segment_123",
  "topic_id": "topic_123",
  "from": "you@yourdomain.com",
  "subject": "Welcome to Acme",
  "preview_text": "string",
  "open_tracking": false,
  "click_tracking": false,
  "status": "draft",
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "variable_fallbacks": {},
  "excluded_contact_ids": [
    "string"
  ],
  "total_recipients": 1,
  "sent_count": 1,
  "failed_count": 1,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Edit a campaign while it's still in draft (content edits after that return 409). Beyond content and targeting, you can: send archived to archive/restore it; set variable_fallbacks (fill-only per-variable defaults) and/or excluded_contact_ids to resolve a send blocked by missing variables — the API equivalent of the dashboard's pre-send fix; set topic_id to scope it to a subscription topic (or null to clear); and set open_tracking/click_tracking (tri-state; null inherits the template/domain default). See what's blocking a send with Validate a campaign. Requires the campaigns:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Request body6 fields
FieldTypeDescription
archivedboolean
topic_idobject
open_trackingobject
click_trackingobject
variable_fallbacksobject
excluded_contact_idsstring[]
Response · 200 fields20 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
template_idrequiredstring
segment_idrequiredstring
topic_idrequiredobject
fromrequiredstring
subjectrequiredobject
preview_textrequiredobject
open_trackingrequiredobject
click_trackingrequiredobject
statusrequiredenumdraft | scheduled | sending | paused | sent | canceled | failed
scheduled_atrequiredobject
variable_fallbacksrequiredobject
excluded_contact_idsrequiredstring[]
total_recipientsrequirednumber
sent_countrequirednumber
failed_countrequirednumber
created_atrequiredstring
updated_atrequiredstring
200401404409422

Delete a campaign

DELETE/api/v1/email/campaigns/{id}

DELETE /email/campaigns/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/email/campaigns/cmp_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Permanently delete a campaign that's in draft or a terminal state (sent, canceled, failed). A campaign that's mid-flight can't be deleted — stop it first with Cancel a campaign. Requires the campaigns:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
204401404409

Send a campaign

POST/api/v1/email/campaigns/{id}/send

POST /email/campaigns/{id}/send
curl -X POST 'https://www.unitpost.com/api/v1/email/campaigns/cmp_123/send' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "campaign",
  "id": "id_123",
  "name": "Example",
  "template_id": "template_123",
  "segment_id": "segment_123",
  "topic_id": "topic_123",
  "from": "you@yourdomain.com",
  "subject": "Welcome to Acme",
  "preview_text": "string",
  "open_tracking": false,
  "click_tracking": false,
  "status": "draft",
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "variable_fallbacks": {},
  "excluded_contact_ids": [
    "string"
  ],
  "total_recipients": 1,
  "sent_count": 1,
  "failed_count": 1,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Validate a campaign and then send it — immediately, or at its scheduled_at if one is set. If anything blocks the send (unverified domain, missing template variables, empty audience, …) you get a 409 with each blocking reason itemized in error.details[]. To see the full pre-send report without attempting a send, use Validate a campaign; fix variable issues via Update a campaign. A launched campaign can be paused or canceled. Requires the campaigns:send capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields20 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
template_idrequiredstring
segment_idrequiredstring
topic_idrequiredobject
fromrequiredstring
subjectrequiredobject
preview_textrequiredobject
open_trackingrequiredobject
click_trackingrequiredobject
statusrequiredenumdraft | scheduled | sending | paused | sent | canceled | failed
scheduled_atrequiredobject
variable_fallbacksrequiredobject
excluded_contact_idsrequiredstring[]
total_recipientsrequirednumber
sent_countrequirednumber
failed_countrequirednumber
created_atrequiredstring
updated_atrequiredstring
200401404409

Cancel a campaign

POST/api/v1/email/campaigns/{id}/cancel

POST /email/campaigns/{id}/cancel
curl -X POST 'https://www.unitpost.com/api/v1/email/campaigns/cmp_123/cancel' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "campaign",
  "id": "id_123",
  "name": "Example",
  "template_id": "template_123",
  "segment_id": "segment_123",
  "topic_id": "topic_123",
  "from": "you@yourdomain.com",
  "subject": "Welcome to Acme",
  "preview_text": "string",
  "open_tracking": false,
  "click_tracking": false,
  "status": "draft",
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "variable_fallbacks": {},
  "excluded_contact_ids": [
    "string"
  ],
  "total_recipients": 1,
  "sent_count": 1,
  "failed_count": 1,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Cancel a draft, scheduled, or sending campaign. Cancelation is terminal — the campaign can't be restarted afterwards. Messages already sent to recipients are not recalled. If you only want to stop temporarily, use Pause a campaign instead. Requires the campaigns:send capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields20 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
template_idrequiredstring
segment_idrequiredstring
topic_idrequiredobject
fromrequiredstring
subjectrequiredobject
preview_textrequiredobject
open_trackingrequiredobject
click_trackingrequiredobject
statusrequiredenumdraft | scheduled | sending | paused | sent | canceled | failed
scheduled_atrequiredobject
variable_fallbacksrequiredobject
excluded_contact_idsrequiredstring[]
total_recipientsrequirednumber
sent_countrequirednumber
failed_countrequirednumber
created_atrequiredstring
updated_atrequiredstring
200401404409

Pause a campaign

POST/api/v1/email/campaigns/{id}/pause

POST /email/campaigns/{id}/pause
curl -X POST 'https://www.unitpost.com/api/v1/email/campaigns/cmp_123/pause' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "campaign",
  "id": "id_123",
  "name": "Example",
  "template_id": "template_123",
  "segment_id": "segment_123",
  "topic_id": "topic_123",
  "from": "you@yourdomain.com",
  "subject": "Welcome to Acme",
  "preview_text": "string",
  "open_tracking": false,
  "click_tracking": false,
  "status": "draft",
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "variable_fallbacks": {},
  "excluded_contact_ids": [
    "string"
  ],
  "total_recipients": 1,
  "sent_count": 1,
  "failed_count": 1,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Pause a scheduled or sending campaign. A paused send stops cleanly between recipients — already-sent messages are untouched, and no partial email is ever produced. Continue where it left off with Resume a campaign, or stop it for good with Cancel a campaign. Requires the campaigns:send capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields20 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
template_idrequiredstring
segment_idrequiredstring
topic_idrequiredobject
fromrequiredstring
subjectrequiredobject
preview_textrequiredobject
open_trackingrequiredobject
click_trackingrequiredobject
statusrequiredenumdraft | scheduled | sending | paused | sent | canceled | failed
scheduled_atrequiredobject
variable_fallbacksrequiredobject
excluded_contact_idsrequiredstring[]
total_recipientsrequirednumber
sent_countrequirednumber
failed_countrequirednumber
created_atrequiredstring
updated_atrequiredstring
200401404409

Resume a campaign

POST/api/v1/email/campaigns/{id}/resume

POST /email/campaigns/{id}/resume
curl -X POST 'https://www.unitpost.com/api/v1/email/campaigns/cmp_123/resume' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "campaign",
  "id": "id_123",
  "name": "Example",
  "template_id": "template_123",
  "segment_id": "segment_123",
  "topic_id": "topic_123",
  "from": "you@yourdomain.com",
  "subject": "Welcome to Acme",
  "preview_text": "string",
  "open_tracking": false,
  "click_tracking": false,
  "status": "draft",
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "variable_fallbacks": {},
  "excluded_contact_ids": [
    "string"
  ],
  "total_recipients": 1,
  "sent_count": 1,
  "failed_count": 1,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Resume a paused campaign. Sending continues with the recipients that hadn't been reached yet; anyone already delivered to is skipped idempotently, so nobody receives the campaign twice. Pause a send with Pause a campaign. Requires the campaigns:send capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields20 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
template_idrequiredstring
segment_idrequiredstring
topic_idrequiredobject
fromrequiredstring
subjectrequiredobject
preview_textrequiredobject
open_trackingrequiredobject
click_trackingrequiredobject
statusrequiredenumdraft | scheduled | sending | paused | sent | canceled | failed
scheduled_atrequiredobject
variable_fallbacksrequiredobject
excluded_contact_idsrequiredstring[]
total_recipientsrequirednumber
sent_countrequirednumber
failed_countrequirednumber
created_atrequiredstring
updated_atrequiredstring
200401404409

Reschedule a campaign

POST/api/v1/email/campaigns/{id}/reschedule

POST /email/campaigns/{id}/reschedule
curl -X POST 'https://www.unitpost.com/api/v1/email/campaigns/cmp_123/reschedule' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "scheduled_at": "2026-07-01T15:00:00Z"
  }'
Response · 200
{
  "object": "campaign",
  "id": "id_123",
  "name": "Example",
  "template_id": "template_123",
  "segment_id": "segment_123",
  "topic_id": "topic_123",
  "from": "you@yourdomain.com",
  "subject": "Welcome to Acme",
  "preview_text": "string",
  "open_tracking": false,
  "click_tracking": false,
  "status": "draft",
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "variable_fallbacks": {},
  "excluded_contact_ids": [
    "string"
  ],
  "total_recipients": 1,
  "sent_count": 1,
  "failed_count": 1,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Move a scheduled campaign to a new send time. Only campaigns that haven't started sending can be rescheduled — once a send is in flight, pause or cancel it instead. Requires the campaigns:send capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Request body1 field
FieldTypeDescription
scheduled_atrequiredstringNew send time, ISO 8601. Only SCHEDULED campaigns can be moved.
Response · 200 fields20 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
template_idrequiredstring
segment_idrequiredstring
topic_idrequiredobject
fromrequiredstring
subjectrequiredobject
preview_textrequiredobject
open_trackingrequiredobject
click_trackingrequiredobject
statusrequiredenumdraft | scheduled | sending | paused | sent | canceled | failed
scheduled_atrequiredobject
variable_fallbacksrequiredobject
excluded_contact_idsrequiredstring[]
total_recipientsrequirednumber
sent_countrequirednumber
failed_countrequirednumber
created_atrequiredstring
updated_atrequiredstring
200401404409422

Validate a campaign

GET/api/v1/email/campaigns/{id}/validate

GET /email/campaigns/{id}/validate
curl -X GET 'https://www.unitpost.com/api/v1/email/campaigns/cmp_123/validate' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "campaign_validation",
  "total_members": 1,
  "sendable": 0,
  "suppressed": 0,
  "excluded": 0,
  "missing_data": [
    {
      "contact_id": "contact_123",
      "email": "customer@example.com",
      "missing": [
        "string"
      ]
    }
  ],
  "unresolved_variables": [
    "string"
  ],
  "can_send": false
}

Run a non-mutating pre-send check and get the full readiness report: how many contacts will receive the campaign, how many are suppressed or excluded, which contacts are missing template variables, and a final can_send verdict. Nothing is sent or changed. Fix reported variable gaps with Update a campaign (variable_fallbacks / excluded_contact_ids), then launch with Send a campaign. Requires the campaigns:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields8 fields
FieldTypeDescription
objectrequiredenumcampaign_validation
total_membersrequiredinteger
sendablerequiredinteger
suppressedrequiredinteger
excludedrequiredinteger
missing_datarequiredobject[]
unresolved_variablesrequiredstring[]
can_sendrequiredboolean
200401404

Templates

Reusable email designs in the canonical block-based JSON format the dashboard editor produces, with per-recipient {{variables}} filled at send time.

List templates

GET/api/v1/email/templates

GET /email/templates
curl -X GET 'https://www.unitpost.com/api/v1/email/templates' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "template",
      "id": "id_123",
      "name": "Example",
      "subject": "Welcome to Acme",
      "mode": "code",
      "status": "draft",
      "design": "string",
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of your email templates, cursor-paginated. List rows omit the full design document to keep responses small — fetch a single template with Retrieve a template to get it. Reference a template's id when sending with Send or schedule an email or when creating a campaign. Requires the templates:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
subjectrequiredobject
moderequiredenumcode | visual
statusrequiredenumdraft | published
designobject
created_atrequiredstring
updated_atrequiredstring
200401422

Create a template

POST/api/v1/email/templates

POST /email/templates
curl -X POST 'https://www.unitpost.com/api/v1/email/templates' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Welcome",
    "subject": "Welcome!",
    "html": "<h1>Hi {{first_name}}</h1>"
  }'
Response · 201
{
  "object": "template",
  "id": "id_123",
  "name": "Example",
  "subject": "Welcome to Acme",
  "mode": "code",
  "status": "draft",
  "design": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Create a reusable email template from a canonical design document — the same block-based format the dashboard editor produces. Templates support {{variables}} that are filled per-recipient at send time. Once created, preview it in the dashboard, send with it via Send or schedule an email, and iterate with Update a template. Requires the templates:write capability.

Request body5 fields
FieldTypeDescription
namerequiredstring
subjectstring
modeenumcode | visual
categoryenumtransactional | marketing
designobject
Response · 201 fields9 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
subjectrequiredobject
moderequiredenumcode | visual
statusrequiredenumdraft | published
designobject
created_atrequiredstring
updated_atrequiredstring
201401422

Retrieve a template

GET/api/v1/email/templates/{id}

GET /email/templates/{id}
curl -X GET 'https://www.unitpost.com/api/v1/email/templates/tmpl_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "template",
  "id": "id_123",
  "name": "Example",
  "subject": "Welcome to Acme",
  "mode": "code",
  "status": "draft",
  "design": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a single template by id, including the full canonical design document (which List templates omits). Use this to read a design for editing, then save changes with Update a template. Requires the templates:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
subjectrequiredobject
moderequiredenumcode | visual
statusrequiredenumdraft | published
designobject
created_atrequiredstring
updated_atrequiredstring
200401404

Update a template

PATCH/api/v1/email/templates/{id}

PATCH /email/templates/{id}
curl -X PATCH 'https://www.unitpost.com/api/v1/email/templates/tmpl_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "subject": "Welcome aboard!"
  }'
Response · 200
{
  "object": "template",
  "id": "id_123",
  "name": "Example",
  "subject": "Welcome to Acme",
  "mode": "code",
  "status": "draft",
  "design": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Update a template's metadata and/or its design document. Changes take effect for future sends only — emails already sent or queued keep the design they were rendered with. Preview the result in the dashboard before sending; read the current design first with Retrieve a template. Requires the templates:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Request body5 fields
FieldTypeDescription
namestring
subjectobject
modeenumcode | visual
statusenumdraft | published
designobject
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
subjectrequiredobject
moderequiredenumcode | visual
statusrequiredenumdraft | published
designobject
created_atrequiredstring
updated_atrequiredstring
200401404422

Delete a template

DELETE/api/v1/email/templates/{id}

DELETE /email/templates/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/email/templates/tmpl_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Permanently delete a template. Emails already sent with it are unaffected, but future sends that reference its id will fail — update any code or campaigns that still point at it first (see List campaigns). Requires the templates:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
204401404

Brand kits

Read-only Brand Kit profiles (voice, labeled colors, pinned library graphics, core pages) for on-brand template authoring. Writes and URL import stay in the dashboard (Settings → Brand).

List brand kits

GET/api/v1/brand-kits

GET /brand-kits
curl -X GET 'https://www.unitpost.com/api/v1/brand-kits' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "data": [
    {
      "object": "brand_kit",
      "id": "id_123",
      "name": "Example",
      "is_default": false,
      "website_url": "https://example.com",
      "description": "string",
      "tone": "NEUTRAL",
      "voice_notes": "string",
      "industry": "string",
      "core_pages": [
        {
          "url": "https://example.com",
          "title": "string"
        }
      ],
      "extracted": {
        "colors": [
          {
            "hex": "string",
            "label": "string"
          }
        ],
        "logo_urls": [
          "string"
        ],
        "background_urls": [
          "string"
        ],
        "fonts": [
          "string"
        ]
      },
      "graphics": [
        {
          "library_image_id": "library_image_123",
          "role": "logo",
          "label": "string"
        }
      ],
      "source": "string",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "default_id": "default_123"
}

Retrieve every Brand Kit profile in the workspace (up to five named profiles). Each profile holds tone, about, voice notes, labeled colors, pinned logo/background library images, and core pages — the primary brand context for on-brand template authoring. Pass ?name= to filter to one profile by display name (case-insensitive). The response includes default_id for the Default profile. Fetch one by id with Retrieve a brand kit. Writes and URL import stay in the dashboard (Settings → Brand). Requires the templates:read capability.

Parameters1 field
FieldTypeDescription
namestring · queryFilter to a single profile by display name (case-insensitive).
Response · 200 fields14 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
is_defaultrequiredboolean
website_urlrequiredobject
descriptionrequiredobject
tonerequiredobject
voice_notesrequiredobject
industryrequiredobject
core_pagesrequiredobject[]
extractedrequiredobject
graphicsrequiredobject[]
sourcerequiredobject
updated_atrequiredobject
200401403

Retrieve a brand kit

GET/api/v1/brand-kits/{id}

GET /brand-kits/{id}
curl -X GET 'https://www.unitpost.com/api/v1/brand-kits/123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "brand_kit",
  "id": "id_123",
  "name": "Example",
  "is_default": false,
  "website_url": "https://example.com",
  "description": "string",
  "tone": "NEUTRAL",
  "voice_notes": "string",
  "industry": "string",
  "core_pages": [
    {
      "url": "https://example.com",
      "title": "string"
    }
  ],
  "extracted": {
    "colors": [
      {
        "hex": "string",
        "label": "string"
      }
    ],
    "logo_urls": [
      "string"
    ],
    "background_urls": [
      "string"
    ],
    "fonts": [
      "string"
    ]
  },
  "graphics": [
    {
      "library_image_id": "library_image_123",
      "role": "logo",
      "label": "string"
    }
  ],
  "source": "string",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve one Brand Kit profile by id (bk_…), including voice, labeled colors, pinned graphics (library_image_id for /img/{id} in design TSX), and core pages. List every profile with List brand kits. Requires the templates:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields14 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
is_defaultrequiredboolean
website_urlrequiredobject
descriptionrequiredobject
tonerequiredobject
voice_notesrequiredobject
industryrequiredobject
core_pagesrequiredobject[]
extractedrequiredobject
graphicsrequiredobject[]
sourcerequiredobject
updated_atrequiredobject
200401403404

Domains

The domains you send from. Add a domain, publish its DNS records, verify it, and manage its tracking and TLS defaults — sending is blocked until the domain verifies.

List domains

GET/api/v1/email/domains

GET /email/domains
curl -X GET 'https://www.unitpost.com/api/v1/email/domains' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "domain",
      "id": "id_123",
      "name": "Example",
      "status": "pending",
      "records": [
        {
          "type": "string",
          "name": "Example",
          "value": "string",
          "priority": 0
        }
      ],
      "dkim_selector": "string",
      "open_tracking": false,
      "click_tracking": false,
      "tls": "opportunistic",
      "tracking_subdomain": "string",
      "tracking_verified_at": "2026-01-01T00:00:00.000Z",
      "verified_at": "2026-01-01T00:00:00.000Z",
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of your sending domains, cursor-paginated, with each domain's verification status. Add a new one with Add a domain, or fetch a single domain (including its DNS records) with Retrieve a domain. Requires the domains:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields14 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
statusrequiredenumpending | verifying | verified | degraded | failed
recordsrequiredobject
dkim_selectorrequiredobject
open_trackingrequiredobject
click_trackingrequiredobject
tlsrequiredenumopportunistic | enforced
tracking_subdomainrequiredobject
tracking_verified_atrequiredobject
verified_atrequiredobject
created_atrequiredstring
updated_atrequiredstring
200401422

Add a domain

POST/api/v1/email/domains

POST /email/domains
curl -X POST 'https://www.unitpost.com/api/v1/email/domains' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "yourdomain.com"
  }'
Response · 201
{
  "object": "domain",
  "id": "id_123",
  "name": "Example",
  "status": "pending",
  "records": [
    {
      "type": "string",
      "name": "Example",
      "value": "string",
      "priority": 0
    }
  ],
  "dkim_selector": "string",
  "open_tracking": false,
  "click_tracking": false,
  "tls": "opportunistic",
  "tracking_subdomain": "string",
  "tracking_verified_at": "2026-01-01T00:00:00.000Z",
  "verified_at": "2026-01-01T00:00:00.000Z",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Add a domain you'll send email from. The response includes the DNS records (SPF, DKIM, and so on) to publish with your DNS provider. Once they're published, trigger a check with Verify a domain — sending from the domain is blocked until it verifies. Requires the domains:write capability.

Request body1 field
FieldTypeDescription
namerequiredstring
Response · 201 fields14 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
statusrequiredenumpending | verifying | verified | degraded | failed
recordsrequiredobject
dkim_selectorrequiredobject
open_trackingrequiredobject
click_trackingrequiredobject
tlsrequiredenumopportunistic | enforced
tracking_subdomainrequiredobject
tracking_verified_atrequiredobject
verified_atrequiredobject
created_atrequiredstring
updated_atrequiredstring
201401409422

Retrieve a domain

GET/api/v1/email/domains/{id}

GET /email/domains/{id}
curl -X GET 'https://www.unitpost.com/api/v1/email/domains/dom_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "domain",
  "id": "id_123",
  "name": "Example",
  "status": "pending",
  "records": [
    {
      "type": "string",
      "name": "Example",
      "value": "string",
      "priority": 0
    }
  ],
  "dkim_selector": "string",
  "open_tracking": false,
  "click_tracking": false,
  "tls": "opportunistic",
  "tracking_subdomain": "string",
  "tracking_verified_at": "2026-01-01T00:00:00.000Z",
  "verified_at": "2026-01-01T00:00:00.000Z",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a single sending domain by id, including its current verification status and the DNS records you need to publish. Re-run the DNS check with Verify a domain, or change its tracking/TLS settings with Update a domain. Requires the domains:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields14 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
statusrequiredenumpending | verifying | verified | degraded | failed
recordsrequiredobject
dkim_selectorrequiredobject
open_trackingrequiredobject
click_trackingrequiredobject
tlsrequiredenumopportunistic | enforced
tracking_subdomainrequiredobject
tracking_verified_atrequiredobject
verified_atrequiredobject
created_atrequiredstring
updated_atrequiredstring
200401404

Update a domain

PATCH/api/v1/email/domains/{id}

PATCH /email/domains/{id}
curl -X PATCH 'https://www.unitpost.com/api/v1/email/domains/dom_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "click_tracking": true,
    "open_tracking": true
  }'
Response · 200
{
  "object": "domain",
  "id": "id_123",
  "name": "Example",
  "status": "pending",
  "records": [
    {
      "type": "string",
      "name": "Example",
      "value": "string",
      "priority": 0
    }
  ],
  "dkim_selector": "string",
  "open_tracking": false,
  "click_tracking": false,
  "tls": "opportunistic",
  "tracking_subdomain": "string",
  "tracking_verified_at": "2026-01-01T00:00:00.000Z",
  "verified_at": "2026-01-01T00:00:00.000Z",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Update a domain's sending options: open/click tracking defaults, TLS enforcement, and the tracking subdomain. These act as the domain-level defaults that individual emails and campaigns can override (see Update a campaign). Requires the domains:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Request body4 fields
FieldTypeDescription
open_trackingobject
click_trackingobject
tlsenumopportunistic | enforced
tracking_subdomainobject
Response · 200 fields14 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
statusrequiredenumpending | verifying | verified | degraded | failed
recordsrequiredobject
dkim_selectorrequiredobject
open_trackingrequiredobject
click_trackingrequiredobject
tlsrequiredenumopportunistic | enforced
tracking_subdomainrequiredobject
tracking_verified_atrequiredobject
verified_atrequiredobject
created_atrequiredstring
updated_atrequiredstring
200401404422

Delete a domain

DELETE/api/v1/email/domains/{id}

DELETE /email/domains/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/email/domains/dom_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Remove a sending domain from your workspace. Sends that reference an address on this domain will be rejected afterwards, so update your from addresses first. Re-add it any time with Add a domain (you'll need to verify it again). Requires the domains:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
204401404

Verify a domain

POST/api/v1/email/domains/{id}/verify

POST /email/domains/{id}/verify
curl -X POST 'https://www.unitpost.com/api/v1/email/domains/dom_123/verify' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "domain",
  "id": "id_123",
  "name": "Example",
  "status": "pending",
  "records": [
    {
      "type": "string",
      "name": "Example",
      "value": "string",
      "priority": 0
    }
  ],
  "dkim_selector": "string",
  "open_tracking": false,
  "click_tracking": false,
  "tls": "opportunistic",
  "tracking_subdomain": "string",
  "tracking_verified_at": "2026-01-01T00:00:00.000Z",
  "verified_at": "2026-01-01T00:00:00.000Z",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "checks": [
    "string"
  ]
}

Run an on-demand DNS check and advance the domain's verification status. The response includes per-record checks so you can see exactly which DNS records are still missing or wrong. Get the records to publish from Retrieve a domain. Verification also re-runs periodically in the background. Requires the domains:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields15 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
statusrequiredenumpending | verifying | verified | degraded | failed
recordsrequiredobject
dkim_selectorrequiredobject
open_trackingrequiredobject
click_trackingrequiredobject
tlsrequiredenumopportunistic | enforced
tracking_subdomainrequiredobject
tracking_verified_atrequiredobject
verified_atrequiredobject
created_atrequiredstring
updated_atrequiredstring
checksobject[]
200401404409502

Webhooks

Register HTTPS endpoints that receive signed event deliveries (delivered, bounced, opened, clicked, …). The signing secret is returned once on create; verify the signature on every delivery.

List webhook endpoints

GET/api/v1/webhooks

GET /webhooks
curl -X GET 'https://www.unitpost.com/api/v1/webhooks' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "webhook",
      "id": "id_123",
      "name": "Example",
      "url": "https://example.com",
      "events": [
        "string"
      ],
      "status": "enabled",
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of your webhook endpoints, cursor-paginated, with each endpoint's URL, subscribed events, and status. Signing secrets are never returned here — they're shown exactly once by Create a webhook endpoint. Check an endpoint's wiring end-to-end with Send a test event. Requires the webhooks:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields8 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
urlrequiredstring
eventsrequiredstring[]
statusrequiredenumenabled | disabled | suspended
created_atrequiredstring
updated_atrequiredstring
200401403422

Create a webhook endpoint

POST/api/v1/webhooks

POST /webhooks
curl -X POST 'https://www.unitpost.com/api/v1/webhooks' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://example.com/webhooks/unitpost",
    "events": [
      "email.delivered",
      "email.bounced"
    ]
  }'
Response · 201
{
  "object": "webhook",
  "id": "id_123",
  "name": "Example",
  "url": "https://example.com",
  "events": [
    "string"
  ],
  "status": "enabled",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "signing_secret": "string"
}

Register an https URL to receive event notifications (deliveries, bounces, opens, clicks, and more) and choose which events it subscribes to. The response includes the signing_secret exactly once — store it now, as it can never be retrieved again; use it to verify each delivery's signature. Confirm the endpoint works with Send a test event, and adjust its URL or events later with Update a webhook endpoint. Requires the webhooks:manage capability.

Request body3 fields
FieldTypeDescription
urlrequiredstring
eventsrequiredstring[]
namestring
Response · 201 fields9 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
urlrequiredstring
eventsrequiredstring[]
statusrequiredenumenabled | disabled | suspended
created_atrequiredstring
updated_atrequiredstring
signing_secretrequiredstringThe HMAC signing secret. Shown exactly once — store it now; it can never be retrieved again. Use it to verify the signature on every delivery to this endpoint.
201401403409422

Retrieve a webhook endpoint

GET/api/v1/webhooks/{id}

GET /webhooks/{id}
curl -X GET 'https://www.unitpost.com/api/v1/webhooks/wh_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "webhook",
  "id": "id_123",
  "name": "Example",
  "url": "https://example.com",
  "events": [
    "string"
  ],
  "status": "enabled",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a single webhook endpoint by id — its URL, subscribed events, and status. The signing secret is never included; it's returned exactly once when the endpoint is created. Requires the webhooks:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields8 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
urlrequiredstring
eventsrequiredstring[]
statusrequiredenumenabled | disabled | suspended
created_atrequiredstring
updated_atrequiredstring
200401403404

Update a webhook endpoint

PATCH/api/v1/webhooks/{id}

PATCH /webhooks/{id}
curl -X PATCH 'https://www.unitpost.com/api/v1/webhooks/wh_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "events": [
      "email.delivered"
    ]
  }'
Response · 200
{
  "object": "webhook",
  "id": "id_123",
  "name": "Example",
  "url": "https://example.com",
  "events": [
    "string"
  ],
  "status": "enabled",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Update a webhook endpoint's url, subscribed events, name, and/or status (use status to pause and re-enable deliveries without deleting the endpoint). The signing secret is never changed by an update. Verify your changes with Send a test event. Requires the webhooks:manage capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Request body4 fields
FieldTypeDescription
urlstring
eventsstring[]
namestring
statusenumenabled | disabled
Response · 200 fields8 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
urlrequiredstring
eventsrequiredstring[]
statusrequiredenumenabled | disabled | suspended
created_atrequiredstring
updated_atrequiredstring
200401403404409422

Delete a webhook endpoint

DELETE/api/v1/webhooks/{id}

DELETE /webhooks/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/webhooks/wh_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Permanently delete a webhook endpoint and its delivery log. Deliveries stop immediately. The operation is idempotent — deleting an already-deleted endpoint still returns 204. To stop deliveries temporarily instead, set status via Update a webhook endpoint. Requires the webhooks:manage capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
204401403

Send a test event

POST/api/v1/webhooks/{id}/test

POST /webhooks/{id}/test
curl -X POST 'https://www.unitpost.com/api/v1/webhooks/wh_123/test' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "webhook_test",
  "delivered": false,
  "response_status": 0,
  "event_type": "string",
  "error": "string"
}

Fire a sample event at the endpoint synchronously and return the receiver's response so you can debug your handler end-to-end. The probe is signed exactly like a real delivery (verify it the same way) but is not persisted to the delivery log. Configure the endpoint itself with Update a webhook endpoint. Requires the webhooks:manage capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
Response · 200 fields5 fields
FieldTypeDescription
objectrequiredenumwebhook_test
deliveredrequiredbooleanWhether the endpoint accepted the probe (2xx).
response_statusrequiredintegerThe HTTP status the endpoint returned, or null on a transport error.
event_typerequiredstringThe sample event type that was sent.
errorrequiredstringA transport/error message when delivery failed, else null.
200401403404

SMS

Beta. Send transactional and marketing SMS, and track each message's delivery lifecycle. Marketing sends require recipient consent and honor recipient-local quiet hours. Returns not_found on workspaces where the channel isn't enabled yet.

List SMS messages

GET/api/v1/sms

GET /sms
curl -X GET 'https://www.unitpost.com/api/v1/sms' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "sms",
      "id": "id_123",
      "to": "customer@example.com",
      "from": "you@yourdomain.com",
      "direction": "outbound",
      "status": "queued",
      "type": "transactional",
      "body": "string",
      "segments": 0,
      "country": "string",
      "scheduled_at": "2026-01-01T00:00:00.000Z",
      "last_error": "string",
      "created_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve your workspace's SMS messages (outbound and inbound), newest-first and cursor-paginated. Use a message's id with Retrieve an SMS message to read its delivery state. Requires the sms:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields13 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
torequiredstring
fromrequiredobject
directionrequiredenumoutbound | inbound
statusrequiredenumqueued | scheduled | sending | sent | delivered | failed | suppressed | canceled | received
typerequiredenumtransactional | marketing
bodyrequiredobject
segmentsrequiredinteger
countryrequiredobject
scheduled_atrequiredobject
last_errorrequiredobject
created_atrequiredstring
200401403422

Send an SMS message

POST/api/v1/sms

POST /sms
curl -X POST 'https://www.unitpost.com/api/v1/sms' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ }'
Response · 200
{
  "object": "sms",
  "id": "id_123",
  "to": "customer@example.com",
  "from": "you@yourdomain.com",
  "direction": "outbound",
  "status": "queued",
  "type": "transactional",
  "body": "string",
  "segments": 0,
  "country": "string",
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "last_error": "string",
  "created_at": "2026-01-01T00:00:00.000Z"
}

Send a single SMS — now, or at a future time by passing scheduled_at. The recipient must be a valid E.164 number; marketing sends additionally require the recipient's prior express consent on record and honor recipient-local quiet hours (they are deferred to the next allowed window, never dropped). Billing is per segment. Read delivery state back with Retrieve an SMS message. Requires the sms:send capability.

Request body4 fields
FieldTypeDescription
torequiredstringRecipient phone number in E.164 format, e.g. `+15551234567`.
bodyrequiredstringMessage text. Billing is per segment (160 GSM-7 chars single-segment, 153 per segment concatenated; 70/67 for Unicode).
typeenumtransactional | marketingMarketing sends require the recipient's prior express consent on record and honor recipient-local quiet hours.
scheduled_atstringISO-8601 time to send at. Omit to send immediately.
Response · 200 fields13 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
torequiredstring
fromrequiredobject
directionrequiredenumoutbound | inbound
statusrequiredenumqueued | scheduled | sending | sent | delivered | failed | suppressed | canceled | received
typerequiredenumtransactional | marketing
bodyrequiredobject
segmentsrequiredinteger
countryrequiredobject
scheduled_atrequiredobject
last_errorrequiredobject
created_atrequiredstring
200401403422429

Retrieve an SMS message

GET/api/v1/sms/{id}

GET /sms/{id}
curl -X GET 'https://www.unitpost.com/api/v1/sms/123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "sms",
  "id": "id_123",
  "to": "customer@example.com",
  "from": "you@yourdomain.com",
  "direction": "outbound",
  "status": "queued",
  "type": "transactional",
  "body": "string",
  "segments": 0,
  "country": "string",
  "scheduled_at": "2026-01-01T00:00:00.000Z",
  "last_error": "string",
  "created_at": "2026-01-01T00:00:00.000Z"
}

Retrieve one SMS message's current status, delivery state, and segment count by id. Requires the sms:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · pathThe SMS message id (`sms_…`).
Response · 200 fields13 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
torequiredstring
fromrequiredobject
directionrequiredenumoutbound | inbound
statusrequiredenumqueued | scheduled | sending | sent | delivered | failed | suppressed | canceled | received
typerequiredenumtransactional | marketing
bodyrequiredobject
segmentsrequiredinteger
countryrequiredobject
scheduled_atrequiredobject
last_errorrequiredobject
created_atrequiredstring
200401403404

API keys

Create and revoke the API keys your integrations authenticate with. These endpoints are session-gated (dashboard cookie + apikeys:manage) and NOT usable with a Bearer API key.

List API keys

GET/api/v1/api-keys

GET /api-keys
curl -X GET 'https://www.unitpost.com/api/v1/api-keys' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "api_key",
      "id": "id_123",
      "name": "Example",
      "prefix": "string",
      "scope": "full",
      "capabilities": [
        "string"
      ],
      "last_used_at": "2026-01-01T00:00:00.000Z",
      "revoked_at": "2026-01-01T00:00:00.000Z",
      "created_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of your workspace's API keys — the key prefix and metadata only, never the secret. Create a new key with Create an API key or revoke one with Revoke an API key. This endpoint is session-gated: it requires a dashboard session and cannot be called with a Bearer API key.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
prefixrequiredstring
scoperequiredenumfull | sending | read_only
capabilitiesrequiredstring[]
last_used_atrequiredobject
revoked_atrequiredobject
created_atrequiredstring
200401403422

Create an API key

POST/api/v1/api-keys

POST /api-keys
curl -X POST 'https://www.unitpost.com/api/v1/api-keys' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Production server",
    "capabilities": [
      "emails:send"
    ]
  }'
Response · 201
{
  "object": "api_key",
  "id": "id_123",
  "name": "Example",
  "prefix": "string",
  "scope": "full",
  "capabilities": [
    "string"
  ],
  "last_used_at": "2026-01-01T00:00:00.000Z",
  "revoked_at": "2026-01-01T00:00:00.000Z",
  "created_at": "2026-01-01T00:00:00.000Z",
  "key": "string"
}

Create an API key scoped to the capabilities you choose. The response includes the plaintext key exactly once — store it securely now, because it can never be retrieved again (only revoked). This endpoint is session-gated: it requires a dashboard session and cannot be called with a Bearer API key.

Request body3 fields
FieldTypeDescription
namerequiredstring
scopeenumfull | sending | read_only
capabilitiesenum[]
Response · 201 fields10 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
namerequiredstring
prefixrequiredstring
scoperequiredenumfull | sending | read_only
capabilitiesrequiredstring[]
last_used_atrequiredobject
revoked_atrequiredobject
created_atrequiredstring
keyrequiredstringThe plaintext secret. Shown exactly once — store it now; it can never be retrieved again.
201401403422

Revoke an API key

DELETE/api/v1/api-keys/{id}

DELETE /api-keys/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/api-keys/key_123' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Permanently revoke an API key. Requests made with it start failing with 401 immediately, and revocation cannot be undone — create a replacement first with Create an API key if you're rotating. This endpoint is session-gated: it requires a dashboard session and cannot be called with a Bearer API key.

Parameters1 field
FieldTypeDescription
idrequiredstring · path
204401403404

Suppressions

Address-level send blocks — the hard "never email this address" list. Combines engine-written bounce/complaint entries, your own additions, and read-only Unitpost-wide blocks.

List suppressed addresses

GET/api/v1/suppressions

GET /suppressions
curl -X GET 'https://www.unitpost.com/api/v1/suppressions' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "suppression",
      "id": "id_123",
      "email": "customer@example.com",
      "scope": "workspace",
      "reason": "bounce",
      "detail": "string",
      "note": "string",
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of email addresses that are blocked from receiving your mail, newest-first and cursor-paginated. The list includes your workspace's own suppressions AND the read-only Unitpost-wide (platform) entries that would block a send — so it's the complete answer to "why isn't this address getting mail?". Pass ?scope=workspace to hide platform rows. Add an address with Suppress an address or lift a block with Un-suppress an address. Requires the suppressions:read capability.

Parameters4 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
scopestring · queryFilter by scope. `workspace` returns only your own suppressions; omit (or any other value) to also include read-only `platform` rows.
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
emailrequiredstring
scoperequiredenumworkspace | platform
reasonrequiredenumbounce | complaint | unsubscribe | manual | import | api
detailrequiredobject
noterequiredobject
created_atrequiredstring
updated_atrequiredstring
200401403422

Suppress an address

POST/api/v1/suppressions

POST /suppressions
curl -X POST 'https://www.unitpost.com/api/v1/suppressions' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ }'
Response · 200
{
  "object": "suppression",
  "id": "id_123",
  "email": "customer@example.com",
  "scope": "workspace",
  "reason": "bounce",
  "detail": "string",
  "note": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Block one address ({ email }) or up to 1,000 at once ({ emails: [...] }) from receiving any future sends from your workspace. The operation is idempotent — re-suppressing an existing address returns 200 rather than a conflict. The bulk form returns an { object: "list", added, skipped, total } summary. Suppression is the hard stop; to let recipients opt out of just one kind of email, use topics (see Create a topic). Undo with Un-suppress an address. Requires the suppressions:write capability.

Request body4 fields
FieldTypeDescription
emailstring
emailsstring[]
reasonenummanual | import | api
notestring
200201401403422

Retrieve a suppressed address

GET/api/v1/suppressions/{id}

GET /suppressions/{id}
curl -X GET 'https://www.unitpost.com/api/v1/suppressions/123' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "suppression",
  "id": "id_123",
  "email": "customer@example.com",
  "scope": "workspace",
  "reason": "bounce",
  "detail": "string",
  "note": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve one suppression by id (supp_…) or by the email address itself. Returns your own workspace row, or a read-only platform row when the address is blocked Unitpost-wide. Lift a workspace block with Un-suppress an address. Requires the suppressions:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · pathA suppression id (`supp_…`) or the email address.
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring
idrequiredstring
emailrequiredstring
scoperequiredenumworkspace | platform
reasonrequiredenumbounce | complaint | unsubscribe | manual | import | api
detailrequiredobject
noterequiredobject
created_atrequiredstring
updated_atrequiredstring
200401403404

Un-suppress an address

DELETE/api/v1/suppressions/{id}

DELETE /suppressions/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/suppressions/123' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Remove a workspace suppression by id or email so the address can receive your mail again. The email form is idempotent — removing an address that isn't suppressed still returns 204. Unitpost-wide (platform) entries can't be removed here (403) — contact support. Check what's blocking an address first with Retrieve a suppressed address. Requires the suppressions:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · pathA suppression id (`supp_…`) or the email address.
204401403404

Usage

Your workspace's current billing-period usage: emails sent, and on paid plans the dollar sending wallet (monthly allowance, spend, purchased credit, overage) in integer USD cents.

Retrieve usage

GET/api/v1/usage

GET /usage
curl -X GET 'https://www.unitpost.com/api/v1/usage' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Response · 200
{
  "object": "usage",
  "plan": "string",
  "period_start": "string",
  "period_end": "string",
  "emails": {
    "sent": 0,
    "included": 0,
    "daily_cap": 0,
    "sent_today": 0
  },
  "wallet": {
    "allowance_cents": 0,
    "used_cents": 0,
    "carryover_cents": 0,
    "purchased_credit_cents": 0,
    "overage_cents": 0,
    "overage_cap_cents": 0,
    "email_rate_per_1k_cents": 0
  }
}

Retrieve your workspace's current billing-period usage snapshot: the plan, the period window, and the email counter. On paid plans the response also carries the dollar sending wallet — the monthly allowance included with the plan, spend so far, any upgrade carryover, the non-expiring purchased-credit balance, and billable overage — all as integer USD cents. On the free plan wallet is null and the emails object carries the monthly quota and daily cap instead. Amounts are read from the same meters that gate sending, so this matches the dashboard exactly. Requires the emails:read capability.

Response · 200 fields6 fields
FieldTypeDescription
objectrequiredstring
planrequiredstring
period_startrequiredstring
period_endrequiredstring
emailsrequiredobject
walletrequiredobject
200401403

Component library

Build emails from a small set of cross-client-tested components — Section, Row, Heading, Button, and more. Installation, live previews, and props on their own page.