Email starts with a domain you own. Add it under Domains, publish the DNS records, and wait until it verifies. Live sends use a from address on that domain.
A template is optional. You can send HTML or text in the request, or save the message on Templates and pass its id. The Quickstart is the full first-send walkthrough, including the API key.
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.
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>"`).
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.
Rather than inlining HTML on every send, save a template once and reference it by ID. Pass `template.variables` and any {{variable}} in the template is substituted at send time.
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.
Author templates in the visual editor, or in your app with `import { Heading, render } from "@unitpost/email/react"` and POST `{ name, html: render(<Email />), subject }`. Manage them under Templates. Every block and layout is documented at /components.
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.
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.
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.
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.
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.
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
Field
Type
Description
fromrequired
string
Sender 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.
torequired
object
—
subject
string
—
cc
object
—
bcc
object
—
reply_to
object
—
in_reply_to
string
—
headers
object
—
tags
object[]
—
template
object
Published template plus variable values. Mutually exclusive with `html` and `text`.
html
string
HTML MIME body. It is sanitized for email safety and may be supplied with `text` to create a multipart alternative. This field does not parse Markdown.
text
string
Plain-text MIME body. Newline characters are preserved; Markdown and rich-text styling are not interpreted. It may be supplied with `html` as the fallback alternative. When `html` is sent without `text`, a plain-text alternative is derived from the visible HTML content; an explicit `text` value is always sent unchanged.
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.
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
Field
Type
Description
idrequired
string · path
—
Response · 200 fields7 fields
Field
Type
Description
objectrequired
enumbatch
—
idrequired
string
—
countrequired
integer
—
scheduled_atrequired
string
—
created_atrequired
string
—
status_countsrequired
object
Live child counts keyed by status (e.g. { delivered: 98, failed: 2 }).
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
Field
Type
Description
idrequired
string · path
—
Response · 200 fields7 fields
Field
Type
Description
objectrequired
enumbatch
—
idrequired
string
—
countrequired
integer
—
scheduled_atrequired
string
—
created_atrequired
string
—
status_countsrequired
object
Live child counts keyed by status (e.g. { delivered: 98, failed: 2 }).
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. Messages submitted over **SMTP** can also be retrieved by the RFC 5322 Message-ID your mail library stamped on them (URL-encode it, e.g. /email/%3Cabc%40example.com%3E; angle brackets optional) — the id in the SMTP 250 Queued as … reply works too. To change a scheduled or queued email, use Cancel or reschedule an email. Requires the emails:read capability.
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.
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
Field
Type
Description
days
integer · query
Window size in days (1–365). Defaults to 30; values above 365 are rejected.
Response · 200 fields4 fields
Field
Type
Description
windowDaysrequired
integer
The rolling window, in days.
clampedToRetentionDays
integer
Non-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).
totalsrequired
object
—
ratesrequired
object
Percentages (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.
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.
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.
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
Field
Type
Description
idrequired
string · path
—
attachmentIdrequired
string · path
—
Response · 200 fields3 fields
Field
Type
Description
objectrequired
enumreceived_attachment_url
—
urlrequired
string
A signed, expiring URL to download the attachment bytes.
expires_atrequired
string
ISO 8601 expiry of the signed URL (~5 minutes out).
200401403404503
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.
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.
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.
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.
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.
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.
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.
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.
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
Field
Type
Description
idrequired
string · path
—
Request body3 fields
Field
Type
Description
topic_id
string
—
topic
string
—
subscribedrequired
boolean
—
Response · 201 fields5 fields
Field
Type
Description
objectrequired
string
—
topic_idrequired
string
—
subscribedrequired
boolean
—
statusrequired
enumsubscribed | unsubscribed
—
sourcerequired
enumuser | 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.
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.
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 published template_id (campaigns have no 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.
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.
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.
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.
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.
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.
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.
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.
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
Field
Type
Description
idrequired
string · path
—
Request body1 field
Field
Type
Description
scheduled_atrequired
string
New send time, ISO 8601. Only SCHEDULED campaigns can be moved.
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
Field
Type
Description
idrequired
string · path
—
Response · 200 fields8 fields
Field
Type
Description
objectrequired
enumcampaign_validation
—
total_membersrequired
integer
—
sendablerequired
integer
—
suppressedrequired
integer
—
excludedrequired
integer
—
missing_datarequired
object[]
—
unresolved_variablesrequired
string[]
—
can_sendrequired
boolean
—
200401404
Templates
Reusable email HTML with per-recipient {{variables}} filled at send time. The dashboard visual editor is a separate surface and still uses the structured document internally.
Retrieve a list of your email templates, cursor-paginated. List rows omit html 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.
Create a reusable email template from html (and optional subject). {{variables}} in the HTML fill per-recipient at send time. The body is stored in code mode so you can open it in the dashboard. Once created, send with it via Send or schedule an email, and iterate with Update a template. Requires the templates:write capability.
Request body5 fields
Field
Type
Description
namerequired
string
—
subject
string
—
html
string
The email body as HTML (compose with @unitpost/email `render()` or any HTML; `{{variables}}` fill at send). Pass exactly one of `html` or `text`.
text
string
Simple email body instead of `html` — plain text exactly as you'd type it into Gmail: lines, a blank line between paragraphs, '- ' or '1. ' list items, and inline <b>, <i>, <u>, <a href="…"> for emphasis and links. No Markdown (it stays literal). Sent as a bare mail-client fragment with no layout or colours; marketing templates get a one-line unsubscribe footer at send time. Max 20,000 characters. Pass exactly one of `html` or `text`.
Retrieve a single template by id, including html (which List templates omits). Use this to read the body, then save changes with Update a template. Requires the templates:read capability.
Update a template's metadata and/or html. Changes take effect for future sends only — emails already sent or queued keep the body they were rendered with. Writing html switches the template to code mode. Read the current body first with Retrieve a template. Requires the templates:write capability.
Parameters1 field
Field
Type
Description
idrequired
string · path
—
Request body5 fields
Field
Type
Description
name
string
—
subject
object
—
status
enumdraft | published
—
html
string
The email body as HTML (compose with @unitpost/email `render()` or any HTML; `{{variables}}` fill at send). Pass exactly one of `html` or `text`.
text
string
Simple email body instead of `html` — plain text exactly as you'd type it into Gmail: lines, a blank line between paragraphs, '- ' or '1. ' list items, and inline <b>, <i>, <u>, <a href="…"> for emphasis and links. No Markdown (it stays literal). Sent as a bare mail-client fragment with no layout or colours; marketing templates get a one-line unsubscribe footer at send time. Max 20,000 characters. Pass exactly one of `html` or `text`.
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
Field
Type
Description
idrequired
string · 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).
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
Field
Type
Description
name
string · query
Filter to a single profile by display name (case-insensitive).
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
Field
Type
Description
idrequired
string · path
—
Response · 200 fields14 fields
Field
Type
Description
objectrequired
string
—
idrequired
string
—
namerequired
string
—
is_defaultrequired
boolean
—
website_urlrequired
object
—
descriptionrequired
object
—
tonerequired
object
—
voice_notesrequired
object
—
industryrequired
object
—
core_pagesrequired
object[]
—
extractedrequired
object
—
graphicsrequired
object[]
—
sourcerequired
object
—
updated_atrequired
object
—
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.
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.
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.
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.
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.
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.
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.