API reference
Every endpoint, at https://api.heraldmail.dev, with your key as a bearer token. The machine-readable description is at /openapi.json (OpenAPI 3.1). Errors are described in Errors; lists take limit and after.
Emails
Send an email
post/v1/emails
| Field | Type | What it is |
|---|---|---|
from | string | "Name <address>", on one of the project’s domains (the template’s, when it has one) |
torequired | string | string[] | One address, or up to 50 |
cc | string | string[] | |
bcc | string | string[] | |
reply_to | string | string[] | |
subject | string | The template’s, when one is named |
html | string | |
text | string | Made from the HTML when left out |
headers | object | Your own headers; routing and authentication headers are refused |
attachments | object[] | Up to 100 files, 40 MB in all; content_id makes one an inline image |
tags | object[] | For the log and webhooks |
scheduled_at | string (date-time) | Send later: an ISO 8601 time with its offset |
stream | "transactional" | "marketing" | transactional (the default), or marketing: a Proclamation, with one-click unsubscribe |
template | object | A published template by id, filled in with variables, instead of html and text |
List emails
get/v1/emails
Get an email and its events
get/v1/emails/{id}
Cancel a scheduled email
post/v1/emails/{id}/cancel
Domains
Add a sending domain
post/v1/domains
| Field | Type | What it is |
|---|---|---|
namerequired | string | The domain mail is sent from: mentor.app |
ed25519 | boolean | Sign with Ed25519 (RFC 8463) as well as RSA |
List domains
get/v1/domains
Get a domain and its DNS records
get/v1/domains/{id}
Change open and click tracking, the tracking host, or Postmaster Tools
patch/v1/domains/{id}
| Field | Type | What it is |
|---|---|---|
open_tracking | boolean | |
click_tracking | boolean | |
tracking_host | string | null | A name under the domain, CNAMEd to Herald’s tracking host; null for the shared one |
postmaster_tools | boolean | Read what Gmail reports for it (add Herald’s reader in Postmaster Tools first) |
Check a domain’s DNS records now
post/v1/domains/{id}/verify
What Gmail reported for it (Postmaster Tools), the last 30 days
get/v1/domains/{id}/reputation
API keys
Create an API key (shown once)
post/v1/api-keys
| Field | Type | What it is |
|---|---|---|
namerequired | string | |
scopes | "send" | "read" | "full"[] | send (the default), read, or full: everything, keys included |
domain_ids | string (uuid)[] | Only these domains may send |
expires_at | string (date-time) | |
ip_allowlist | string (cidrv4) | string (cidrv6)[] | Only from these addresses (CIDR) |
List API keys
get/v1/api-keys
Revoke an API key
delete/v1/api-keys/{id}
Webhooks
Add a webhook endpoint (its signing secret is shown once)
post/v1/webhooks
| Field | Type | What it is |
|---|---|---|
urlrequired | string | HTTPS in live mode |
events | "email.sent" | "email.delivered" | "email.delivery_delayed" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "email.failed" | "email.suppressed" | "email.unsubscribed" | "email.received" | "broadcast.started" | "broadcast.paused" | "broadcast.resumed" | "broadcast.sent" | "broadcast.cancelled" | "automation.webhook"[] | The events to send; every one when left out |
description | string |
List webhook endpoints
get/v1/webhooks
Get a webhook endpoint
get/v1/webhooks/{id}
Change, enable or disable a webhook endpoint
patch/v1/webhooks/{id}
| Field | Type |
|---|---|
url | string |
events | "email.sent" | "email.delivered" | "email.delivery_delayed" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "email.failed" | "email.suppressed" | "email.unsubscribed" | "email.received" | "broadcast.started" | "broadcast.paused" | "broadcast.resumed" | "broadcast.sent" | "broadcast.cancelled" | "automation.webhook"[] |
description | string | null |
status | "enabled" | "disabled" |
Delete a webhook endpoint
delete/v1/webhooks/{id}
Make a new signing secret (the old one signs for 24 hours more)
post/v1/webhooks/{id}/rotate-secret
Send a test event to this endpoint
post/v1/webhooks/{id}/test
| Field | Type |
|---|---|
type | "email.sent" | "email.delivered" | "email.delivery_delayed" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "email.failed" | "email.suppressed" | "email.unsubscribed" | "email.received" | "broadcast.started" | "broadcast.paused" | "broadcast.resumed" | "broadcast.sent" | "broadcast.cancelled" | "automation.webhook" |
List deliveries and their attempts
get/v1/webhooks/{id}/deliveries
Send a delivery again
post/v1/webhooks/{id}/deliveries/{delivery}/replay
Inbound
Take mail for an address on a verified domain
post/v1/inbound/routes
| Field | Type |
|---|---|
domain_idrequired | string (uuid) |
local_partrequired | string |
List inbound routes
get/v1/inbound/routes
Delete an inbound route
delete/v1/inbound/routes/{id}
List inbound mail
get/v1/inbound
Get an inbound email (its content arrives by webhook)
get/v1/inbound/{id}
Contacts
Add a contact, or update the one with that address (optionally asking them to confirm first)
post/v1/contacts
| Field | Type | What it is |
|---|---|---|
emailrequired | string | |
first_name | string | null | |
last_name | string | null | |
fields | object | Custom fields, by key; null removes one |
topics | object[] | Topics they chose, or left |
audience_ids | string (uuid)[] | Lists to add them to |
double_opt_in | object |
Add or update up to 1,000 contacts, all or none
post/v1/contacts/batch
| Field | Type |
|---|---|
contactsrequired | object[] |
Get a contact, by id or address, with their topics, audiences and consent record
get/v1/contacts/{id}
Change a contact’s names, fields, topics or audiences
patch/v1/contacts/{id}
| Field | Type | What it is |
|---|---|---|
first_name | string | null | |
last_name | string | null | |
fields | object | Custom fields, by key; null removes one |
topics | object[] | Topics they chose, or left |
audience_ids | string (uuid)[] | Lists to add them to |
Erase a contact: only a suppression hash stays, so they are never mailed again
delete/v1/contacts/{id}
Everything held about a contact, for a subject access request
get/v1/contacts/{id}/export
Suppressions
Stop mailing an address: everything, or Proclamations only
post/v1/suppressions
| Field | Type | What it is |
|---|---|---|
emailrequired | string | The address to stop mailing |
scope | "all" | "marketing" | all (the default): every email · marketing: Proclamations only |
List who this project no longer mails, and why, newest first
get/v1/suppressions
Whether an address is suppressed, and why
get/v1/suppressions/{email}
Take an address off the list: entries you added, and hard bounces (never a complaint, an unsubscribe or an erasure)
delete/v1/suppressions/{email}
Contact imports
Import contacts from a CSV, with your attestation that they agreed
post/v1/contact-imports
| Field | Type |
|---|---|
filenamerequired | string |
csvrequired | string |
mappingrequired | object |
sourcerequired | string |
audience_id | string (uuid) |
topic_ids | string (uuid)[] |
List recent imports
get/v1/contact-imports
Get an import: its status and counts
get/v1/contact-imports/{id}
Contact fields
Add a typed custom field
post/v1/contact-fields
| Field | Type |
|---|---|
keyrequired | string |
labelrequired | string |
typerequired | "text" | "number" | "boolean" | "date" |
List custom fields
get/v1/contact-fields
Delete a field, and its values from every contact
delete/v1/contact-fields/{key}
Topics
Add a topic
post/v1/topics
| Field | Type |
|---|---|
namerequired | string |
description | string | null |
default_subscription | "opt_in" | "opt_out" |
public | boolean |
List topics and their subscribers
get/v1/topics
Change a topic
patch/v1/topics/{id}
| Field | Type |
|---|---|
name | string |
description | string | null |
default_subscription | "opt_in" | "opt_out" |
public | boolean |
Delete a topic
delete/v1/topics/{id}
Audiences
List audiences
get/v1/audiences
Get an audience
get/v1/audiences/{id}
Delete an audience (its contacts stay)
delete/v1/audiences/{id}
Add existing contacts to an audience
post/v1/audiences/{id}/contacts
| Field | Type |
|---|---|
contact_ids | string (uuid)[] |
emails | string[] |
List an audience’s contacts
get/v1/audiences/{id}/contacts
Take a contact out of an audience
delete/v1/audiences/{id}/contacts/{contact}
Segments
List segments
get/v1/segments
Count the contacts some rules pick out, without saving them
post/v1/segments/count
Get a segment and how many contacts it picks out now
get/v1/segments/{id}
Delete a segment
delete/v1/segments/{id}
List the contacts a segment picks out
get/v1/segments/{id}/contacts
Templates
Add a template (its draft starts from a simple layout)
post/v1/templates
| Field | Type |
|---|---|
namerequired | string |
stream | "transactional" | "marketing" |
kind | "blocks" | "html" |
subject | string |
html | string |
List templates
get/v1/templates
Get a template: its draft and its latest published version
get/v1/templates/{id}
Change the draft (published versions never change)
patch/v1/templates/{id}
| Field | Type |
|---|---|
name | string |
stream | "transactional" | "marketing" |
subject | string |
preheader | string | null |
from | string | null |
reply_to | string | null |
document | any |
html | string |
text | string | null |
sample | object |
Delete a template and its versions
delete/v1/templates/{id}
Publish the draft as the next version, once it renders with its sample variables
post/v1/templates/{id}/publish
Fill in a version with variables, without sending
post/v1/templates/{id}/render
| Field | Type |
|---|---|
version | "draft" | integer |
variables | object |
Brand kit
Change the brand kit
patch/v1/brand-kit
| Field | Type |
|---|---|
background | string |
surface | string |
text_colour | string |
accent | string |
font | "sans" | "serif" | "mono" |
company | string | null |
address | string | null |
logo_asset_id | string (uuid) | null |
Events
Record something a contact did: it can start automations, and end their waits
post/v1/events
| Field | Type | What it is |
|---|---|---|
namerequired | string | |
contactrequired | string | Their address, or their id |
properties | object | Anything about it, for branches and templates |
Automations
Add an automation (a draft graph: a trigger, then steps)
post/v1/automations
| Field | Type |
|---|---|
namerequired | string |
graph | any |
List automations
get/v1/automations
Get an automation: its draft and what stops it being published, its live version, and counts per step
get/v1/automations/{id}
Change the draft (runs going keep their version)
patch/v1/automations/{id}
| Field | Type |
|---|---|
name | string |
graph | any |
Delete one that was never published
delete/v1/automations/{id}
Publish the draft as the next version; it enrols by it from now on
post/v1/automations/{id}/publish
Pause it: nobody new enrols, and runs wait where they are
post/v1/automations/{id}/pause
Resume a paused automation
post/v1/automations/{id}/resume
Archive it: every run still going stops
post/v1/automations/{id}/archive
Enrol a contact by hand, by its re-entry rule
post/v1/automations/{id}/runs
| Field | Type |
|---|---|
contactrequired | string |
events | string[] |
Its recent runs
get/v1/automations/{id}/runs
Walk the draft for a contact without doing anything: the path, the waits, the emails
post/v1/automations/{id}/test
| Field | Type |
|---|---|
contactrequired | string |
events | string[] |
Automation runs
Stop a run
post/v1/automation-runs/{id}/cancel
Broadcasts
Add a broadcast (a draft: a template or HTML of its own, a list or a segment, a topic)
post/v1/broadcasts
| Field | Type |
|---|---|
namerequired | string |
template_id | string (uuid) | null |
template_version | integer | null |
subject | string | null |
preheader | string | null |
html | string | null |
text | string | null |
from | string | null |
reply_to | string | null |
audience_id | string (uuid) | null |
segment_id | string (uuid) | null |
topic_id | string (uuid) | null |
scheduled_at | string (date-time) | null |
time_zone | string | null |
per_hour | integer | null |
List broadcasts
get/v1/broadcasts
Get a broadcast
get/v1/broadcasts/{id}
Change a draft
patch/v1/broadcasts/{id}
| Field | Type |
|---|---|
name | string |
template_id | string (uuid) | null |
template_version | integer | null |
subject | string | null |
preheader | string | null |
html | string | null |
text | string | null |
from | string | null |
reply_to | string | null |
audience_id | string (uuid) | null |
segment_id | string (uuid) | null |
topic_id | string (uuid) | null |
scheduled_at | string (date-time) | null |
time_zone | string | null |
per_hour | integer | null |
Delete a draft
delete/v1/broadcasts/{id}
Run the pre-flight checks: who it reaches, the content, the sender, the topic, the schedule, holds
post/v1/broadcasts/{id}/check
Send it, or schedule it: the checks again, then the snapshot, a canary, and the rest in batches
post/v1/broadcasts/{id}/send
Pause it: nothing more is sent, and the mail servers suspend what they still hold of it
post/v1/broadcasts/{id}/pause
| Field | Type |
|---|---|
reason | string |
Resume a paused broadcast where it stopped
post/v1/broadcasts/{id}/resume
Cancel a draft, scheduled or paused broadcast for good
post/v1/broadcasts/{id}/cancel
Its progress: the snapshot, what was sent and what happened, by provider against the breakers
get/v1/broadcasts/{id}/progress
Template images
Upload a template image (PNG, JPEG, GIF or WebP; re-encoded, never SVG)
post/v1/assets
| Field | Type |
|---|---|
filenamerequired | string |
contentrequired | string (base64) |
List template images
get/v1/assets
Get a template image and its address
get/v1/assets/{id}
Delete a template image (it stops being served within a day)
delete/v1/assets/{id}