Developers
Sync contacts, send campaigns, get events back
Sync contacts and orders, launch campaigns and automations, and get every open, click and unsubscribe pushed back to your server. One REST API, one key.
https://mintmailer.app/api/v1Every path is relative to it, so /contacts is https://mintmailer.app/api/v1/contacts.Quickstart
Three steps from zero to your first contact. Every snippet reads your key from MINTMAIL_API_KEY, so nothing secret ends up in a script you paste around.
Create an API key
In the app, open Settings → API keys and create a key with the scopes you need — each endpoint below shows the one it requires. The key is shown once.
Shellexport MINTMAIL_API_KEY="rvz_live_…"// .env MINTMAIL_API_KEY=rvz_live_…# .env MINTMAIL_API_KEY=rvz_live_…Make your first request
List your contacts. A
200with{data, has_more, next_cursor}means the key works.GET /contactscurl https://mintmailer.app/api/v1/contacts \ -H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/contacts", { method: "GET", headers: { Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`, }, }) const { data } = await res.json()import os, requests res = requests.get( "https://mintmailer.app/api/v1/contacts", headers={ "Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}", }, ) data = res.json()["data"]Write something — safely
Add a contact. The
Idempotency-Keymakes a retry return the first response instead of creating a duplicate.POST /contactscurl -X POST https://mintmailer.app/api/v1/contacts \ -H "Authorization: Bearer $MINTMAIL_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: a-unique-key-per-request" \ -d '{ "email": "[email protected]", "first_name": "Ada" }'const res = await fetch("https://mintmailer.app/api/v1/contacts", { method: "POST", headers: { Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "a-unique-key-per-request", }, body: JSON.stringify({ "email": "[email protected]", "first_name": "Ada" }), }) const { data } = await res.json()import os, requests res = requests.post( "https://mintmailer.app/api/v1/contacts", headers={ "Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}", "Idempotency-Key": "a-unique-key-per-request", }, json={ "email": "[email protected]", "first_name": "Ada", }, ) data = res.json()["data"]
Concepts
Authentication
Send your key as Authorization: Bearer rvz_live_…. A key belongs to one organisation and carries a fixed set of scopes, chosen when it was created; every operation states the scope it requires. A key without the scope gets 403, and a request for another organisation's row gets 404 rather than 403 — confirming that an id exists elsewhere is itself a disclosure.
Pagination
List endpoints return {data, has_more, next_cursor}. Pass next_cursor back as cursor to walk forward; the default page is 50 rows and the maximum is 100. The cursor is a keyset, not an offset, so a row inserted while you page cannot cause a skip or a repeat — which is what makes an incremental sync safe. It is opaque: pass it back unmodified and do not parse it.
Idempotency
Send an Idempotency-Key header on any write and a retry of that exact request returns the first response byte for byte, including its status and any id, rather than creating a second row. Idempotent-Replay on the response says which of the two happened. Omitting the header is allowed and gives you ordinary at-least-once semantics.
Rate limits
Per key, not per organisation, so one integration cannot spend another's budget. RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset are on every response, so you never have to exceed the limit to discover it.
Versioning
v1 changes additively only: an endpoint, an optional field or a new enum value may appear at any time, and nothing is ever removed, renamed or retyped inside a major version. Parse leniently and a release cannot break you. No version is withdrawn on less than twelve months' notice, announced with a Sunset header on every affected response long before it stops answering.
Webhooks guide
Get every open, click, bounce and unsubscribe POSTed to your server as it happens — no polling.
| Event | Fires when |
|---|---|
email.delivered | The receiving mail server accepted the message. |
email.opened | The recipient opened the message. |
email.clicked | The recipient clicked a tracked link. |
email.bounced | The message was rejected — permanently or temporarily. |
email.complained | The recipient marked the message as spam. |
contact.unsubscribed | A contact opted out of your emails. |
campaign.finished | A campaign finished sending to its whole audience. |
email.delivered — The receiving mail server accepted the message.
Content-Type: application/json
Mintmail-Signature: t=1785753000,v1=9cab143e7b23987d59f15fe67fd9ade631ca2ee235b9c9d262b2ac3309da0eb7
Mintmail-Event-Id: evt_01hxyz_email_delivered
Mintmail-Event-Type: email.delivered
User-Agent: MintMail-Webhooks/1{
"id": "evt_01hxyz_email_delivered",
"type": "email.delivered",
"occurred_at": "2026-08-03T10:30:00.000Z",
"organization_id": "org_01hxyz",
"data": {
"campaign_id": "cmp_01hxyz",
"contact_id": "con_01hxyz",
"email": "[email protected]",
"message_recipient_id": "mr_01hxyz",
"machine": false
}
}email.opened — The recipient opened the message.
Content-Type: application/json
Mintmail-Signature: t=1785753000,v1=3ea7fc0e64ea7412d6d1c3115d2f026404141bfa1e3697f276ef0c608feef412
Mintmail-Event-Id: evt_01hxyz_email_opened
Mintmail-Event-Type: email.opened
User-Agent: MintMail-Webhooks/1{
"id": "evt_01hxyz_email_opened",
"type": "email.opened",
"occurred_at": "2026-08-03T10:30:00.000Z",
"organization_id": "org_01hxyz",
"data": {
"campaign_id": "cmp_01hxyz",
"contact_id": "con_01hxyz",
"email": "[email protected]",
"message_recipient_id": "mr_01hxyz",
"machine": false
}
}email.clicked — The recipient clicked a tracked link.
Content-Type: application/json
Mintmail-Signature: t=1785753000,v1=4f005801241ab1c0a1aa202f8ce2f328a3438067a8d7ac774c897276e6be4f60
Mintmail-Event-Id: evt_01hxyz_email_clicked
Mintmail-Event-Type: email.clicked
User-Agent: MintMail-Webhooks/1{
"id": "evt_01hxyz_email_clicked",
"type": "email.clicked",
"occurred_at": "2026-08-03T10:30:00.000Z",
"organization_id": "org_01hxyz",
"data": {
"campaign_id": "cmp_01hxyz",
"contact_id": "con_01hxyz",
"email": "[email protected]",
"message_recipient_id": "mr_01hxyz",
"machine": false,
"link_url": "https://example.com/pricing?utm_source=mintmail"
}
}email.bounced — The message was rejected — permanently or temporarily.
Content-Type: application/json
Mintmail-Signature: t=1785753000,v1=3a59b5c4d6937d242036e7ac719d06e1891ffa0f555c3b7b2b5b2afd5ccf2e5b
Mintmail-Event-Id: evt_01hxyz_email_bounced
Mintmail-Event-Type: email.bounced
User-Agent: MintMail-Webhooks/1{
"id": "evt_01hxyz_email_bounced",
"type": "email.bounced",
"occurred_at": "2026-08-03T10:30:00.000Z",
"organization_id": "org_01hxyz",
"data": {
"campaign_id": "cmp_01hxyz",
"contact_id": "con_01hxyz",
"email": "[email protected]",
"message_recipient_id": "mr_01hxyz",
"machine": false,
"bounce_type": "permanent",
"bounce_subtype": "General",
"diagnostic_code": "smtp; 550 5.1.1 user unknown"
}
}email.complained — The recipient marked the message as spam.
Content-Type: application/json
Mintmail-Signature: t=1785753000,v1=112faf7fa0435780bda1d5f9bd5c9430f79d5f6dd2790f237929e641a2d3f5b5
Mintmail-Event-Id: evt_01hxyz_email_complained
Mintmail-Event-Type: email.complained
User-Agent: MintMail-Webhooks/1{
"id": "evt_01hxyz_email_complained",
"type": "email.complained",
"occurred_at": "2026-08-03T10:30:00.000Z",
"organization_id": "org_01hxyz",
"data": {
"campaign_id": "cmp_01hxyz",
"contact_id": "con_01hxyz",
"email": "[email protected]",
"message_recipient_id": "mr_01hxyz",
"machine": false
}
}contact.unsubscribed — A contact opted out of your emails.
Content-Type: application/json
Mintmail-Signature: t=1785753000,v1=7a830866a2862a0cb1738d9f59bf5c8236b83f16a5cdb60d2e0a0e4c2b051ddf
Mintmail-Event-Id: evt_01hxyz_contact_unsubscribed
Mintmail-Event-Type: contact.unsubscribed
User-Agent: MintMail-Webhooks/1{
"id": "evt_01hxyz_contact_unsubscribed",
"type": "contact.unsubscribed",
"occurred_at": "2026-08-03T10:30:00.000Z",
"organization_id": "org_01hxyz",
"data": {
"contact_id": "con_01hxyz",
"email": "[email protected]",
"campaign_id": "cmp_01hxyz"
}
}campaign.finished — A campaign finished sending to its whole audience.
Content-Type: application/json
Mintmail-Signature: t=1785753000,v1=74310658153aab32669c705dd5958fb79e5104a44f9436abadec1d87f3c9d550
Mintmail-Event-Id: evt_01hxyz_campaign_finished
Mintmail-Event-Type: campaign.finished
User-Agent: MintMail-Webhooks/1{
"id": "evt_01hxyz_campaign_finished",
"type": "campaign.finished",
"occurred_at": "2026-08-03T10:30:00.000Z",
"organization_id": "org_01hxyz",
"data": {
"campaign_id": "cmp_01hxyz",
"name": "November newsletter",
"sent": 4980,
"failed": 20,
"total": 5000
}
}Test your verifier against these exact bytes with the sample secret whsec_docs_example_do_not_use_in_production — skip the five-minute age check, since the timestamp is fixed. Real deliveries are sent compact, without the indentation shown here.
How it works
Rather than polling, register an endpoint — in Settings → Webhooks or with POST /webhooks — and we POST each event you subscribe to as it happens: email.delivered, email.bounced, email.complained, email.opened, email.clicked, contact.unsubscribed and campaign.finished.
Verify the signature
Every request carries Mintmail-Signature: t=<unix seconds>,v1=<hex>, an HMAC-SHA256 over the string {t}.{raw body} keyed with the secret shown once when the endpoint was created. Verify it against the RAW body before parsing — re-serialising JSON changes the bytes and the signature will not match. Reject anything whose t is more than five minutes old, or a payload captured off the wire can be replayed at leisure.
Expect duplicates, not order
Delivery is at-least-once and unordered. An email.opened can reach you before the email.delivered it belongs to, and any event can arrive twice — after a retry, or after our own reconciliation notices a delivery went missing. Key your handler's idempotency on the payload's top-level id, which is stable for the life of the event.
Acknowledge with 2xx
Answer 2xx within ten seconds to acknowledge. Anything else is retried six times over roughly a quarter of an hour with exponential backoff; ten consecutive events that exhaust their retries disable the endpoint and email your admins. Answer 410 Gone to unsubscribe permanently — we stop immediately and do not retry.
Payload
Every delivery is {id, type, occurred_at, organization_id, data} — the WebhookEvent object, not the Webhook subscription resource. Message events (email.*) put campaign_id, contact_id, email, message_recipient_id and machine in data (clicks add link_url; bounces add bounce_type, bounce_subtype, diagnostic_code). contact.unsubscribed is {contact_id, email, campaign_id?}. campaign.finished is {campaign_id, name, sent, failed, total} with no contact fields.
Verification code
# Check a captured delivery by hand. RAW_BODY must be the exact bytes received.
T=$(echo "$SIGNATURE_HEADER" | sed -E 's/.*t=([0-9]+).*/\1/')
V1=$(echo "$SIGNATURE_HEADER" | sed -E 's/.*v1=([0-9a-f]+).*/\1/')
EXPECTED=$(printf '%s.%s' "$T" "$RAW_BODY" \
| openssl dgst -sha256 -hmac "$MINTMAIL_WEBHOOK_SECRET" | awk '{print $NF}')
[ "$EXPECTED" = "$V1" ] && echo "valid" || echo "invalid"import crypto from "node:crypto"
// Express: app.post("/webhooks/mintmail", express.raw({ type: "application/json" }), handler)
export function verify(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")))
const t = Number(parts.t)
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex")
const a = Buffer.from(expected, "hex")
const b = Buffer.from(parts.v1 ?? "", "hex")
return a.length === b.length && crypto.timingSafeEqual(a, b)
}import hashlib, hmac, time
# Flask: verify(request.get_data(), request.headers["Mintmail-Signature"], SECRET)
def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
try:
t = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(
secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Contacts
- get
/contacts - post
/contacts - get
/contacts/{id} - patch
/contacts/{id} - delete
/contacts/{id} - get
/contacts/{id}/lists - get
/contacts/{id}/tags
/contactscontact:viewList contacts
Requires the contact:view scope (View contacts and their details).
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
status | ("subscribed" | "unsubscribed" | "bounced" | "complained" | "pending") | ("subscribed" | "unsubscribed" | "bounced" | "complained" | "pending")[] | |
list_id | string | string[] | |
tag_id | string | string[] | |
search | string |
- Returns
200page of Contactindata- Errors
- 400401403429500Error shape
curl https://mintmailer.app/api/v1/contacts \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/contacts", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/contacts",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "contact",
"email": "[email protected]",
"first_name": "Jamie",
"last_name": "Rivera",
"status": "subscribed",
"custom_fields": {
"plan": "Pro",
"signup_source": "webinar"
},
"opt_in_source": "signup_form",
"opt_in_at": "2026-07-14T09:31:02.000Z",
"last_engaged_at": "2026-07-14T09:31:02.000Z",
"timezone": "timezone",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/contactscontact:editCreate or update a contact
Requires the contact:edit scope (Create and edit contacts).
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
emailrequired | string | |
first_name | string | null | |
last_name | string | null | |
opt_in_source | string | null | |
status | "subscribed" | "unsubscribed" | "bounced" | "complained" | "pending" | |
timezone | string | null |
- Returns
200 or 201Contactindata- Errors
- 400401403409422429500Error shape
curl -X POST https://mintmailer.app/api/v1/contacts \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"email": "[email protected]"
}'const res = await fetch("https://mintmailer.app/api/v1/contacts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"email": "[email protected]"
}),
})
const { data } = await res.json()import os, requests
res = requests.post(
"https://mintmailer.app/api/v1/contacts",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"email": "[email protected]",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "contact",
"email": "[email protected]",
"first_name": "Jamie",
"last_name": "Rivera",
"status": "subscribed",
"custom_fields": {
"plan": "Pro",
"signup_source": "webinar"
},
"opt_in_source": "signup_form",
"opt_in_at": "2026-07-14T09:31:02.000Z",
"last_engaged_at": "2026-07-14T09:31:02.000Z",
"timezone": "timezone",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/contacts/{id}contact:viewRetrieve a contact
Requires the contact:view scope (View contacts and their details).
Path parameters
idrequired | string | The contact's id. |
- Returns
200Contactindata- Errors
- 400401403404429500Error shape
curl https://mintmailer.app/api/v1/contacts/CONTACT_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/contacts/CONTACT_ID", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/contacts/CONTACT_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "contact",
"email": "[email protected]",
"first_name": "Jamie",
"last_name": "Rivera",
"status": "subscribed",
"custom_fields": {
"plan": "Pro",
"signup_source": "webinar"
},
"opt_in_source": "signup_form",
"opt_in_at": "2026-07-14T09:31:02.000Z",
"last_engaged_at": "2026-07-14T09:31:02.000Z",
"timezone": "timezone",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/contacts/{id}contact:editUpdate a contact
Requires the contact:edit scope (Create and edit contacts).
Path parameters
idrequired | string | The contact's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
email | string | |
first_name | string | null | |
last_name | string | null | |
opt_in_source | string | null | |
status | "subscribed" | "unsubscribed" | "bounced" | "complained" | "pending" | |
timezone | string | null |
- Returns
200Contactindata- Errors
- 400401403404409422429500Error shape
curl -X PATCH https://mintmailer.app/api/v1/contacts/CONTACT_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"email": "[email protected]"
}'const res = await fetch("https://mintmailer.app/api/v1/contacts/CONTACT_ID", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"email": "[email protected]"
}),
})
const { data } = await res.json()import os, requests
res = requests.patch(
"https://mintmailer.app/api/v1/contacts/CONTACT_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"email": "[email protected]",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "contact",
"email": "[email protected]",
"first_name": "Jamie",
"last_name": "Rivera",
"status": "subscribed",
"custom_fields": {
"plan": "Pro",
"signup_source": "webinar"
},
"opt_in_source": "signup_form",
"opt_in_at": "2026-07-14T09:31:02.000Z",
"last_engaged_at": "2026-07-14T09:31:02.000Z",
"timezone": "timezone",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/contacts/{id}contact:deleteDelete a contact
Requires the contact:delete scope (Delete contacts).
Path parameters
idrequired | string | The contact's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
- Returns
204no body- Errors
- 400401403404409422429500Error shape
curl -X DELETE https://mintmailer.app/api/v1/contacts/CONTACT_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Idempotency-Key: a-unique-key-per-request"const res = await fetch("https://mintmailer.app/api/v1/contacts/CONTACT_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Idempotency-Key": "a-unique-key-per-request",
},
})import os, requests
res = requests.delete(
"https://mintmailer.app/api/v1/contacts/CONTACT_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
)/contacts/{id}/listslist:viewList the lists a contact belongs to
Requires the list:view scope (View lists).
Path parameters
idrequired | string | The contact's id. |
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
- Returns
200page of Listindata- Errors
- 400401403404429500Error shape
curl https://mintmailer.app/api/v1/contacts/CONTACT_ID/lists \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/contacts/CONTACT_ID/lists", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/contacts/CONTACT_ID/lists",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "list",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}Lists
- get
/lists - post
/lists - get
/lists/{id} - patch
/lists/{id} - delete
/lists/{id} - get
/lists/{id}/contacts - post
/lists/{id}/contacts - delete
/lists/{id}/contacts
/listslist:viewList contact lists
Requires the list:view scope (View lists).
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
name | string |
- Returns
200page of Listindata- Errors
- 400401403429500Error shape
curl https://mintmailer.app/api/v1/lists \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/lists", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/lists",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "list",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/listslist:editCreate a contact list
Requires the list:edit scope (Create and edit lists).
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
namerequired | string | |
description | string | null |
- Returns
201Listindata- Errors
- 400401403409422429500Error shape
curl -X POST https://mintmailer.app/api/v1/lists \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"name": "Newsletter subscribers"
}'const res = await fetch("https://mintmailer.app/api/v1/lists", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"name": "Newsletter subscribers"
}),
})
const { data } = await res.json()import os, requests
res = requests.post(
"https://mintmailer.app/api/v1/lists",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"name": "Newsletter subscribers",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "list",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/lists/{id}list:viewRetrieve a contact list
Requires the list:view scope (View lists).
Path parameters
idrequired | string | The list's id. |
- Returns
200Listindata- Errors
- 400401403404429500Error shape
curl https://mintmailer.app/api/v1/lists/LIST_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/lists/LIST_ID", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/lists/LIST_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "list",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/lists/{id}list:editUpdate a contact list
Requires the list:edit scope (Create and edit lists).
Path parameters
idrequired | string | The list's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
name | string | |
description | string | null |
- Returns
200Listindata- Errors
- 400401403404409422429500Error shape
curl -X PATCH https://mintmailer.app/api/v1/lists/LIST_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"name": "Newsletter subscribers"
}'const res = await fetch("https://mintmailer.app/api/v1/lists/LIST_ID", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"name": "Newsletter subscribers"
}),
})
const { data } = await res.json()import os, requests
res = requests.patch(
"https://mintmailer.app/api/v1/lists/LIST_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"name": "Newsletter subscribers",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "list",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/lists/{id}list:editDelete a contact list, keeping its contacts
Requires the list:edit scope (Create and edit lists).
Path parameters
idrequired | string | The list's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
- Returns
204no body- Errors
- 400401403404409422429500Error shape
curl -X DELETE https://mintmailer.app/api/v1/lists/LIST_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Idempotency-Key: a-unique-key-per-request"const res = await fetch("https://mintmailer.app/api/v1/lists/LIST_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Idempotency-Key": "a-unique-key-per-request",
},
})import os, requests
res = requests.delete(
"https://mintmailer.app/api/v1/lists/LIST_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
)/lists/{id}/contactscontact:viewList the contacts on a list
Requires the contact:view scope (View contacts and their details).
Path parameters
idrequired | string | The list's id. |
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
- Returns
200page of Contactindata- Errors
- 400401403404429500Error shape
curl https://mintmailer.app/api/v1/lists/LIST_ID/contacts \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/lists/LIST_ID/contacts", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/lists/LIST_ID/contacts",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "contact",
"email": "[email protected]",
"first_name": "Jamie",
"last_name": "Rivera",
"status": "subscribed",
"custom_fields": {
"plan": "Pro",
"signup_source": "webinar"
},
"opt_in_source": "signup_form",
"opt_in_at": "2026-07-14T09:31:02.000Z",
"last_engaged_at": "2026-07-14T09:31:02.000Z",
"timezone": "timezone",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/lists/{id}/contactslist:editAdd contacts to a list
Requires the list:edit scope (Create and edit lists).
Path parameters
idrequired | string | The list's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
contact_idsrequired | string[] |
- Returns
200MembershipChangeindata- Errors
- 400401403404409422429500Error shape
curl -X POST https://mintmailer.app/api/v1/lists/LIST_ID/contacts \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"contact_ids": [
"con_01hxyz"
]
}'const res = await fetch("https://mintmailer.app/api/v1/lists/LIST_ID/contacts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"contact_ids": [
"con_01hxyz"
]
}),
})
const { data } = await res.json()import os, requests
res = requests.post(
"https://mintmailer.app/api/v1/lists/LIST_ID/contacts",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"contact_ids": [
"con_01hxyz",
],
},
)
data = res.json()["data"]{
"data": {
"object": "membership_change",
"changed": 128
}
}/lists/{id}/contactslist:editRemove contacts from a list
Requires the list:edit scope (Create and edit lists).
Path parameters
idrequired | string | The list's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
contact_idsrequired | string[] |
- Returns
200MembershipChangeindata- Errors
- 400401403404409422429500Error shape
curl -X DELETE https://mintmailer.app/api/v1/lists/LIST_ID/contacts \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"contact_ids": [
"con_01hxyz"
]
}'const res = await fetch("https://mintmailer.app/api/v1/lists/LIST_ID/contacts", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"contact_ids": [
"con_01hxyz"
]
}),
})import os, requests
res = requests.delete(
"https://mintmailer.app/api/v1/lists/LIST_ID/contacts",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"contact_ids": [
"con_01hxyz",
],
},
){
"data": {
"object": "membership_change",
"changed": 128
}
}Segments
- get
/segments - post
/segments - get
/segments/{id} - patch
/segments/{id} - delete
/segments/{id} - get
/segments/{id}/contacts
/segmentssegment:viewList segments
Requires the segment:view scope (View segments).
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
name | string |
- Returns
200page of Segmentindata- Errors
- 400401403429500Error shape
curl https://mintmailer.app/api/v1/segments \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/segments", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/segments",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "segment",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"rules": {
"version": 1,
"match": "all",
"groups": [
"groups"
]
},
"last_count": 128,
"last_counted_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/segmentssegment:editCreate a segment
Requires the segment:edit scope (Create and edit segments).
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
namerequired | string | |
description | string | null | |
rulesrequired | any |
- Returns
201Segmentindata- Errors
- 400401403409422429500Error shape
curl -X POST https://mintmailer.app/api/v1/segments \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"name": "Newsletter subscribers",
"rules": null
}'const res = await fetch("https://mintmailer.app/api/v1/segments", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"name": "Newsletter subscribers",
"rules": null
}),
})
const { data } = await res.json()import os, requests
res = requests.post(
"https://mintmailer.app/api/v1/segments",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"name": "Newsletter subscribers",
"rules": None,
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "segment",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"rules": {
"version": 1,
"match": "all",
"groups": [
"groups"
]
},
"last_count": 128,
"last_counted_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/segments/{id}segment:viewRetrieve a segment
Requires the segment:view scope (View segments).
Path parameters
idrequired | string | The segment's id. |
- Returns
200Segmentindata- Errors
- 400401403404429500Error shape
curl https://mintmailer.app/api/v1/segments/SEGMENT_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/segments/SEGMENT_ID", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/segments/SEGMENT_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "segment",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"rules": {
"version": 1,
"match": "all",
"groups": [
"groups"
]
},
"last_count": 128,
"last_counted_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/segments/{id}segment:editUpdate a segment
Requires the segment:edit scope (Create and edit segments).
Path parameters
idrequired | string | The segment's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
name | string | |
description | string | null | |
rules | any |
- Returns
200Segmentindata- Errors
- 400401403404409422429500Error shape
curl -X PATCH https://mintmailer.app/api/v1/segments/SEGMENT_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"name": "Newsletter subscribers"
}'const res = await fetch("https://mintmailer.app/api/v1/segments/SEGMENT_ID", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"name": "Newsletter subscribers"
}),
})
const { data } = await res.json()import os, requests
res = requests.patch(
"https://mintmailer.app/api/v1/segments/SEGMENT_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"name": "Newsletter subscribers",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "segment",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"rules": {
"version": 1,
"match": "all",
"groups": [
"groups"
]
},
"last_count": 128,
"last_counted_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/segments/{id}segment:editDelete a segment
Requires the segment:edit scope (Create and edit segments).
Path parameters
idrequired | string | The segment's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
- Returns
204no body- Errors
- 400401403404409422429500Error shape
curl -X DELETE https://mintmailer.app/api/v1/segments/SEGMENT_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Idempotency-Key: a-unique-key-per-request"const res = await fetch("https://mintmailer.app/api/v1/segments/SEGMENT_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Idempotency-Key": "a-unique-key-per-request",
},
})import os, requests
res = requests.delete(
"https://mintmailer.app/api/v1/segments/SEGMENT_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
)/segments/{id}/contactscontact:viewList the contacts a segment selects
Requires the contact:view scope (View contacts and their details).
Path parameters
idrequired | string | The segment's id. |
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
- Returns
200page of Contactindata- Errors
- 400401403404429500Error shape
curl https://mintmailer.app/api/v1/segments/SEGMENT_ID/contacts \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/segments/SEGMENT_ID/contacts", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/segments/SEGMENT_ID/contacts",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "contact",
"email": "[email protected]",
"first_name": "Jamie",
"last_name": "Rivera",
"status": "subscribed",
"custom_fields": {
"plan": "Pro",
"signup_source": "webinar"
},
"opt_in_source": "signup_form",
"opt_in_at": "2026-07-14T09:31:02.000Z",
"last_engaged_at": "2026-07-14T09:31:02.000Z",
"timezone": "timezone",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}Custom fields
/custom_fieldscontact:viewList the organisation's custom field definitions
Requires the contact:view scope (View contacts and their details).
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
- Returns
200page of CustomFieldindata- Errors
- 400401403429500Error shape
curl https://mintmailer.app/api/v1/custom_fields \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/custom_fields", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/custom_fields",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "custom_field",
"key": "plan",
"label": "Plan",
"type": "text",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}Campaigns
- get
/campaigns - post
/campaigns - get
/campaigns/{id} - patch
/campaigns/{id} - delete
/campaigns/{id} - post
/campaigns/{id}/cancel - post
/campaigns/{id}/schedule - get
/campaigns/{id}/stats
/campaignscampaign:viewList campaigns
Requires the campaign:view scope (View campaigns).
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
status | ("draft" | "scheduled" | "dispatching" | "sending" | "testing" | "paused" | "sent" | "cancelled" | "failed") | ("draft" | "scheduled" | "dispatching" | "sending" | "testing" | "paused" | "sent" | "cancelled" | "failed")[] |
- Returns
200page of Campaignindata- Errors
- 400401403429500Error shape
curl https://mintmailer.app/api/v1/campaigns \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/campaigns", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/campaigns",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "campaign",
"name": "Weekly digest",
"status": "draft",
"subject": "Your July statement",
"preheader": "Everything that moved this month, in one place.",
"sender_identity_id": "cly3k9f2b0000q8n1h4d7g5xz",
"from_name": "Acme",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"email_document_id": "cly3k9f2b0000q8n1h4d7g5xz",
"audience": {
"include": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
},
"exclude": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
}
},
"track_opens": true,
"track_clicks": true,
"scheduled_at": "2026-07-14T09:31:02.000Z",
"send_started_at": "2026-07-14T09:31:02.000Z",
"send_completed_at": "2026-07-14T09:31:02.000Z",
"recipient_count": 128,
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/campaignscampaign:editCreate a campaign
Requires the campaign:edit scope (Create and edit campaigns).
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
namerequired | string |
- Returns
201Campaignindata- Errors
- 400401403409422429500Error shape
curl -X POST https://mintmailer.app/api/v1/campaigns \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"name": "Newsletter subscribers"
}'const res = await fetch("https://mintmailer.app/api/v1/campaigns", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"name": "Newsletter subscribers"
}),
})
const { data } = await res.json()import os, requests
res = requests.post(
"https://mintmailer.app/api/v1/campaigns",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"name": "Newsletter subscribers",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "campaign",
"name": "Weekly digest",
"status": "draft",
"subject": "Your July statement",
"preheader": "Everything that moved this month, in one place.",
"sender_identity_id": "cly3k9f2b0000q8n1h4d7g5xz",
"from_name": "Acme",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"email_document_id": "cly3k9f2b0000q8n1h4d7g5xz",
"audience": {
"include": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
},
"exclude": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
}
},
"track_opens": true,
"track_clicks": true,
"scheduled_at": "2026-07-14T09:31:02.000Z",
"send_started_at": "2026-07-14T09:31:02.000Z",
"send_completed_at": "2026-07-14T09:31:02.000Z",
"recipient_count": 128,
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/campaigns/{id}campaign:viewRetrieve a campaign
Requires the campaign:view scope (View campaigns).
Path parameters
idrequired | string | The campaign's id. |
- Returns
200Campaignindata- Errors
- 400401403404429500Error shape
curl https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "campaign",
"name": "Weekly digest",
"status": "draft",
"subject": "Your July statement",
"preheader": "Everything that moved this month, in one place.",
"sender_identity_id": "cly3k9f2b0000q8n1h4d7g5xz",
"from_name": "Acme",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"email_document_id": "cly3k9f2b0000q8n1h4d7g5xz",
"audience": {
"include": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
},
"exclude": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
}
},
"track_opens": true,
"track_clicks": true,
"scheduled_at": "2026-07-14T09:31:02.000Z",
"send_started_at": "2026-07-14T09:31:02.000Z",
"send_completed_at": "2026-07-14T09:31:02.000Z",
"recipient_count": 128,
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/campaigns/{id}campaign:editUpdate a draft campaign
Requires the campaign:edit scope (Create and edit campaigns).
Path parameters
idrequired | string | The campaign's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
name | string | |
subject | string | null | |
preheader | string | null | |
sender_identity_id | string | |
reply_to | "" | string | null | |
email_document_id | string | |
audience | object | |
include | object | |
exclude | object | |
track_opens | boolean | |
track_clicks | boolean |
- Returns
200Campaignindata- Errors
- 400401403404409422429500Error shape
curl -X PATCH https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"name": "Newsletter subscribers"
}'const res = await fetch("https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"name": "Newsletter subscribers"
}),
})
const { data } = await res.json()import os, requests
res = requests.patch(
"https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"name": "Newsletter subscribers",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "campaign",
"name": "Weekly digest",
"status": "draft",
"subject": "Your July statement",
"preheader": "Everything that moved this month, in one place.",
"sender_identity_id": "cly3k9f2b0000q8n1h4d7g5xz",
"from_name": "Acme",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"email_document_id": "cly3k9f2b0000q8n1h4d7g5xz",
"audience": {
"include": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
},
"exclude": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
}
},
"track_opens": true,
"track_clicks": true,
"scheduled_at": "2026-07-14T09:31:02.000Z",
"send_started_at": "2026-07-14T09:31:02.000Z",
"send_completed_at": "2026-07-14T09:31:02.000Z",
"recipient_count": 128,
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/campaigns/{id}campaign:deleteDelete a draft or cancelled campaign
Requires the campaign:delete scope (Delete campaigns).
Path parameters
idrequired | string | The campaign's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
- Returns
204no body- Errors
- 400401403404409422429500Error shape
curl -X DELETE https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Idempotency-Key: a-unique-key-per-request"const res = await fetch("https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Idempotency-Key": "a-unique-key-per-request",
},
})import os, requests
res = requests.delete(
"https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
)/campaigns/{id}/cancelcampaign:sendCancel a scheduled or sending campaign
Requires the campaign:send scope (Send and schedule campaigns).
Path parameters
idrequired | string | The campaign's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
- Returns
200Campaignindata- Errors
- 400401403404409422429500Error shape
curl -X POST https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID/cancel \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Idempotency-Key: a-unique-key-per-request"const res = await fetch("https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID/cancel", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Idempotency-Key": "a-unique-key-per-request",
},
})
const { data } = await res.json()import os, requests
res = requests.post(
"https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID/cancel",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "campaign",
"name": "Weekly digest",
"status": "draft",
"subject": "Your July statement",
"preheader": "Everything that moved this month, in one place.",
"sender_identity_id": "cly3k9f2b0000q8n1h4d7g5xz",
"from_name": "Acme",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"email_document_id": "cly3k9f2b0000q8n1h4d7g5xz",
"audience": {
"include": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
},
"exclude": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
}
},
"track_opens": true,
"track_clicks": true,
"scheduled_at": "2026-07-14T09:31:02.000Z",
"send_started_at": "2026-07-14T09:31:02.000Z",
"send_completed_at": "2026-07-14T09:31:02.000Z",
"recipient_count": 128,
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/campaigns/{id}/schedulecampaign:sendSchedule a campaign, or send it now
Requires the campaign:send scope (Send and schedule campaigns).
Path parameters
idrequired | string | The campaign's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
scheduled_at | string | When to send, as an ISO 8601 instant. Omit to send now. |
- Returns
200Campaignindata- Errors
- 400401403404409422429500Error shape
curl -X POST https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID/schedule \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"scheduled_at": "2026-11-01T09:00:00Z"
}'const res = await fetch("https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID/schedule", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"scheduled_at": "2026-11-01T09:00:00Z"
}),
})
const { data } = await res.json()import os, requests
res = requests.post(
"https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID/schedule",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"scheduled_at": "2026-11-01T09:00:00Z",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "campaign",
"name": "Weekly digest",
"status": "draft",
"subject": "Your July statement",
"preheader": "Everything that moved this month, in one place.",
"sender_identity_id": "cly3k9f2b0000q8n1h4d7g5xz",
"from_name": "Acme",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"email_document_id": "cly3k9f2b0000q8n1h4d7g5xz",
"audience": {
"include": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
},
"exclude": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
}
},
"track_opens": true,
"track_clicks": true,
"scheduled_at": "2026-07-14T09:31:02.000Z",
"send_started_at": "2026-07-14T09:31:02.000Z",
"send_completed_at": "2026-07-14T09:31:02.000Z",
"recipient_count": 128,
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/campaigns/{id}/statsanalytics:viewRetrieve a campaign's engagement totals and rates
Requires the analytics:view scope (View reports and analytics).
Path parameters
idrequired | string | The campaign's id. |
- Returns
200CampaignStatsindata- Errors
- 400401403404429500Error shape
curl https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID/stats \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID/stats", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/campaigns/CAMPAIGN_ID/stats",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": {
"object": "campaign_stats",
"campaign_id": "cly3k9f2b0000q8n1h4d7g5xz",
"sent": 128,
"delivered": 128,
"opened": 128,
"unique_opened": 128,
"clicked": 128,
"unique_clicked": 128,
"bounced": 128,
"soft_bounced": 128,
"complained": 128,
"unsubscribed": 128,
"failed": 128,
"open_rate": 0.42,
"click_rate": 0.42,
"click_to_open_rate": 0.42,
"bounce_rate": 0.42,
"hard_bounce_rate": 0.42,
"complaint_rate": 0.42,
"unsubscribe_rate": 0.42,
"updated_at": "2026-07-14T09:31:02.000Z"
}
}Automations
- get
/automations - get
/automations/{id} - post
/automations/{id}/activate - post
/automations/{id}/enroll - post
/automations/{id}/pause
/automationsautomation:viewList automations
Requires the automation:view scope (View automations).
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
status | ("draft" | "active" | "paused" | "archived") | ("draft" | "active" | "paused" | "archived")[] |
- Returns
200page of Automationindata- Errors
- 400401403429500Error shape
curl https://mintmailer.app/api/v1/automations \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/automations", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/automations",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "automation",
"name": "Weekly digest",
"status": "draft",
"version": 1,
"allow_reentry": true,
"definition": {
"version": 1,
"entryNodeId": "entryNodeId",
"nodes": {}
},
"activated_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/automations/{id}automation:viewRetrieve an automation
Requires the automation:view scope (View automations).
Path parameters
idrequired | string | The automation's id. |
- Returns
200Automationindata- Errors
- 400401403404429500Error shape
curl https://mintmailer.app/api/v1/automations/AUTOMATION_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/automations/AUTOMATION_ID", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/automations/AUTOMATION_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "automation",
"name": "Weekly digest",
"status": "draft",
"version": 1,
"allow_reentry": true,
"definition": {
"version": 1,
"entryNodeId": "entryNodeId",
"nodes": {}
},
"activated_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/automations/{id}/activateautomation:editTurn an automation on, or resume a paused one
Requires the automation:edit scope (Create, edit and activate automations).
Path parameters
idrequired | string | The automation's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
- Returns
200Automationindata- Errors
- 400401403404409422429500Error shape
curl -X POST https://mintmailer.app/api/v1/automations/AUTOMATION_ID/activate \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Idempotency-Key: a-unique-key-per-request"const res = await fetch("https://mintmailer.app/api/v1/automations/AUTOMATION_ID/activate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Idempotency-Key": "a-unique-key-per-request",
},
})
const { data } = await res.json()import os, requests
res = requests.post(
"https://mintmailer.app/api/v1/automations/AUTOMATION_ID/activate",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "automation",
"name": "Weekly digest",
"status": "draft",
"version": 1,
"allow_reentry": true,
"definition": {
"version": 1,
"entryNodeId": "entryNodeId",
"nodes": {}
},
"activated_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/automations/{id}/enrollautomation:editEnrol contacts into an active automation
Requires the automation:edit scope (Create, edit and activate automations).
Path parameters
idrequired | string | The automation's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
contact_idsrequired | string[] |
- Returns
200Enrollmentindata- Errors
- 400401403404409422429500Error shape
curl -X POST https://mintmailer.app/api/v1/automations/AUTOMATION_ID/enroll \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"contact_ids": [
"con_01hxyz"
]
}'const res = await fetch("https://mintmailer.app/api/v1/automations/AUTOMATION_ID/enroll", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"contact_ids": [
"con_01hxyz"
]
}),
})
const { data } = await res.json()import os, requests
res = requests.post(
"https://mintmailer.app/api/v1/automations/AUTOMATION_ID/enroll",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"contact_ids": [
"con_01hxyz",
],
},
)
data = res.json()["data"]{
"data": {
"object": "enrollment",
"requested": 128,
"enrolled": 128
}
}/automations/{id}/pauseautomation:editPause an automation, holding everyone inside it
Requires the automation:edit scope (Create, edit and activate automations).
Path parameters
idrequired | string | The automation's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
- Returns
200Automationindata- Errors
- 400401403404409422429500Error shape
curl -X POST https://mintmailer.app/api/v1/automations/AUTOMATION_ID/pause \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Idempotency-Key: a-unique-key-per-request"const res = await fetch("https://mintmailer.app/api/v1/automations/AUTOMATION_ID/pause", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Idempotency-Key": "a-unique-key-per-request",
},
})
const { data } = await res.json()import os, requests
res = requests.post(
"https://mintmailer.app/api/v1/automations/AUTOMATION_ID/pause",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "automation",
"name": "Weekly digest",
"status": "draft",
"version": 1,
"allow_reentry": true,
"definition": {
"version": 1,
"entryNodeId": "entryNodeId",
"nodes": {}
},
"activated_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}Orders
/ordersorder:viewList orders
Requires the order:view scope (View orders and attributed revenue).
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
email | string | |
status | "paid" | "refunded" | "partially_refunded" | "cancelled" | |
attributed | "true" | "false" |
- Returns
200page of Orderindata- Errors
- 400401403429500Error shape
curl https://mintmailer.app/api/v1/orders \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/orders", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/orders",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "order",
"external_id": "cly3k9f2b0000q8n1h4d7g5xz",
"email": "[email protected]",
"contact_id": "cly3k9f2b0000q8n1h4d7g5xz",
"status": "paid",
"total_cents": 128,
"refunded_cents": 128,
"currency": "currency",
"occurred_at": "2026-07-14T09:31:02.000Z",
"source": "source",
"attributed_campaign_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attributed_automation_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attributed_node_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attribution_rule": "attribution_rule",
"attributed_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/ordersorder:editCreate or update an order
Requires the order:edit scope (Create and update orders).
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
external_idrequired | string | |
emailrequired | string | |
status | "paid" | "refunded" | "partially_refunded" | "cancelled" | |
total_centsrequired | integer | |
refunded_cents | integer | |
currencyrequired | string | |
occurred_atrequired | string | |
source | string | null |
- Returns
200 or 201Orderindata- Errors
- 400401403409422429500Error shape
curl -X POST https://mintmailer.app/api/v1/orders \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"external_id": "order_1001",
"email": "[email protected]",
"total_cents": 4900,
"currency": "USD",
"occurred_at": "2026-11-01T09:00:00Z"
}'const res = await fetch("https://mintmailer.app/api/v1/orders", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"external_id": "order_1001",
"email": "[email protected]",
"total_cents": 4900,
"currency": "USD",
"occurred_at": "2026-11-01T09:00:00Z"
}),
})
const { data } = await res.json()import os, requests
res = requests.post(
"https://mintmailer.app/api/v1/orders",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"external_id": "order_1001",
"email": "[email protected]",
"total_cents": 4900,
"currency": "USD",
"occurred_at": "2026-11-01T09:00:00Z",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "order",
"external_id": "cly3k9f2b0000q8n1h4d7g5xz",
"email": "[email protected]",
"contact_id": "cly3k9f2b0000q8n1h4d7g5xz",
"status": "paid",
"total_cents": 128,
"refunded_cents": 128,
"currency": "currency",
"occurred_at": "2026-07-14T09:31:02.000Z",
"source": "source",
"attributed_campaign_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attributed_automation_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attributed_node_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attribution_rule": "attribution_rule",
"attributed_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/orders/{id}order:viewRetrieve an order
Requires the order:view scope (View orders and attributed revenue).
Path parameters
idrequired | string | The order's id. |
- Returns
200Orderindata- Errors
- 400401403404429500Error shape
curl https://mintmailer.app/api/v1/orders/ORDER_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/orders/ORDER_ID", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/orders/ORDER_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "order",
"external_id": "cly3k9f2b0000q8n1h4d7g5xz",
"email": "[email protected]",
"contact_id": "cly3k9f2b0000q8n1h4d7g5xz",
"status": "paid",
"total_cents": 128,
"refunded_cents": 128,
"currency": "currency",
"occurred_at": "2026-07-14T09:31:02.000Z",
"source": "source",
"attributed_campaign_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attributed_automation_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attributed_node_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attribution_rule": "attribution_rule",
"attributed_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}Webhooks
/webhookswebhook:manageList webhook endpoints
Requires the webhook:manage scope (Add and remove webhook endpoints).
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
- Returns
200page of Webhookindata- Errors
- 400401403429500Error shape
curl https://mintmailer.app/api/v1/webhooks \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/webhooks", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/webhooks",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "webhook",
"url": "url",
"description": "Everyone who opened something in the last 90 days.",
"events": [
"email.delivered"
],
"status": "enabled",
"disabled_reason": "disabled_reason",
"consecutive_failures": 128,
"last_success_at": "2026-07-14T09:31:02.000Z",
"last_failure_at": "2026-07-14T09:31:02.000Z",
"secret": "secret",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/webhookswebhook:manageCreate a webhook endpoint
Requires the webhook:manage scope (Add and remove webhook endpoints).
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
urlrequired | string | |
eventsrequired | ("email.delivered" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "contact.unsubscribed" | "campaign.finished")[] | |
description | string |
- Returns
201Webhookindata- Errors
- 400401403409422429500Error shape
Outbound event delivery
The JSON body we later POST to your URL — not the create response. See the webhooks guide for every event and how to verify it, or WebhookEvent for the field list.
{
"id": "evt_01hxyz_email_opened",
"type": "email.opened",
"occurred_at": "2026-08-03T10:30:00.000Z",
"organization_id": "org_01hxyz",
"data": {
"campaign_id": "cmp_01hxyz",
"contact_id": "con_01hxyz",
"email": "[email protected]",
"message_recipient_id": "mr_01hxyz",
"machine": false
}
}Delivery body
idrequired | string | Stable source-event id. Delivery is at-least-once — key your idempotency on this. |
typerequired | "email.delivered" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "contact.unsubscribed" | "campaign.finished" | The dotted event name you subscribed to, e.g. email.opened. |
occurred_atrequired | string | When the event happened, not when we delivered it. |
organization_idrequired | string | Your organisation. |
datarequired | object | Message-event fields. For contact.unsubscribed, data is {contact_id, email, campaign_id?}. For campaign.finished, data is {campaign_id, name, sent, failed, total} — no contact fields. |
campaign_idrequired | string | The campaign that sent the message. |
contact_idrequired | string | Our id for the person. |
emailrequired | string | null | The contact's address — match this to a contact in your system without calling the API back. Null only if the contact was deleted between the event and the emit. |
message_recipient_idrequired | string | This specific send of the message. |
machinerequired | boolean | True when an open or click came from a scanner or link unfurler rather than a person. Always present on message events, including when false. Machine hits are excluded from every figure in the app. |
link_url | string | null | Clicks only — the URL that was clicked. |
bounce_type | string | null | Bounces only — permanent, transient, or undetermined. |
bounce_subtype | string | null | Bounces only — provider subtype when available. |
diagnostic_code | string | null | Bounces only — SMTP diagnostic when available. |
curl -X POST https://mintmailer.app/api/v1/webhooks \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"url": "https://example.com/webhooks/mintmail",
"events": [
"email.opened",
"email.clicked"
]
}'const res = await fetch("https://mintmailer.app/api/v1/webhooks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"url": "https://example.com/webhooks/mintmail",
"events": [
"email.opened",
"email.clicked"
]
}),
})
const { data } = await res.json()import os, requests
res = requests.post(
"https://mintmailer.app/api/v1/webhooks",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"url": "https://example.com/webhooks/mintmail",
"events": [
"email.opened",
"email.clicked",
],
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "webhook",
"url": "url",
"description": "Everyone who opened something in the last 90 days.",
"events": [
"email.delivered"
],
"status": "enabled",
"disabled_reason": "disabled_reason",
"consecutive_failures": 128,
"last_success_at": "2026-07-14T09:31:02.000Z",
"last_failure_at": "2026-07-14T09:31:02.000Z",
"secret": "secret",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/webhooks/{id}webhook:manageRetrieve a webhook endpoint
Requires the webhook:manage scope (Add and remove webhook endpoints).
Path parameters
idrequired | string | The webhook's id. |
- Returns
200Webhookindata- Errors
- 400401403404429500Error shape
curl https://mintmailer.app/api/v1/webhooks/WEBHOOK_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY"const res = await fetch("https://mintmailer.app/api/v1/webhooks/WEBHOOK_ID", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
},
})
const { data } = await res.json()import os, requests
res = requests.get(
"https://mintmailer.app/api/v1/webhooks/WEBHOOK_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "webhook",
"url": "url",
"description": "Everyone who opened something in the last 90 days.",
"events": [
"email.delivered"
],
"status": "enabled",
"disabled_reason": "disabled_reason",
"consecutive_failures": 128,
"last_success_at": "2026-07-14T09:31:02.000Z",
"last_failure_at": "2026-07-14T09:31:02.000Z",
"secret": "secret",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/webhooks/{id}webhook:manageUpdate a webhook endpoint
Requires the webhook:manage scope (Add and remove webhook endpoints).
Path parameters
idrequired | string | The webhook's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
url | string | |
events | ("email.delivered" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "contact.unsubscribed" | "campaign.finished")[] | |
description | string | null | |
status | "enabled" | "disabled_by_user" |
- Returns
200Webhookindata- Errors
- 400401403404409422429500Error shape
curl -X PATCH https://mintmailer.app/api/v1/webhooks/WEBHOOK_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a-unique-key-per-request" \
-d '{
"url": "https://example.com/webhooks/mintmail"
}'const res = await fetch("https://mintmailer.app/api/v1/webhooks/WEBHOOK_ID", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "a-unique-key-per-request",
},
body: JSON.stringify({
"url": "https://example.com/webhooks/mintmail"
}),
})
const { data } = await res.json()import os, requests
res = requests.patch(
"https://mintmailer.app/api/v1/webhooks/WEBHOOK_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
json={
"url": "https://example.com/webhooks/mintmail",
},
)
data = res.json()["data"]{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "webhook",
"url": "url",
"description": "Everyone who opened something in the last 90 days.",
"events": [
"email.delivered"
],
"status": "enabled",
"disabled_reason": "disabled_reason",
"consecutive_failures": 128,
"last_success_at": "2026-07-14T09:31:02.000Z",
"last_failure_at": "2026-07-14T09:31:02.000Z",
"secret": "secret",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/webhooks/{id}webhook:manageDelete a webhook endpoint
Requires the webhook:manage scope (Add and remove webhook endpoints).
Path parameters
idrequired | string | The webhook's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
- Returns
204no body- Errors
- 400401403404409422429500Error shape
curl -X DELETE https://mintmailer.app/api/v1/webhooks/WEBHOOK_ID \
-H "Authorization: Bearer $MINTMAIL_API_KEY" \
-H "Idempotency-Key: a-unique-key-per-request"const res = await fetch("https://mintmailer.app/api/v1/webhooks/WEBHOOK_ID", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.MINTMAIL_API_KEY}`,
"Idempotency-Key": "a-unique-key-per-request",
},
})import os, requests
res = requests.delete(
"https://mintmailer.app/api/v1/webhooks/WEBHOOK_ID",
headers={
"Authorization": f"Bearer {os.environ['MINTMAIL_API_KEY']}",
"Idempotency-Key": "a-unique-key-per-request",
},
)Object reference
Every REST resource carries an object field naming its type, so a client holding one response can tell what it has without inferring from the shape. WebhookEvent is the outbound push body (no object field) — what we POST to your endpoint, distinct from the Webhook subscription resource.
Automation
Fields
idrequired | string | |
objectrequired | "automation" | |
namerequired | string | |
statusrequired | "draft" | "active" | "paused" | "archived" | |
versionrequired | integer | |
allow_reentryrequired | boolean | |
definitionrequired | object | The flow, in the grammar automationDefinitionSchema defines. Read-only in v1. |
versionrequired | integer | |
entryNodeIdrequired | string | null | |
nodesrequired | object | |
activated_atrequired | string | null | |
created_atrequired | string | |
updated_atrequired | string |
Campaign
Fields
idrequired | string | |
objectrequired | "campaign" | |
namerequired | string | |
statusrequired | "draft" | "scheduled" | "dispatching" | "sending" | "testing" | "paused" | "sent" | "cancelled" | "failed" | |
subjectrequired | string | null | |
preheaderrequired | string | null | |
sender_identity_idrequired | string | null | |
from_namerequired | string | null | |
from_emailrequired | string | null | |
reply_torequired | string | null | |
email_document_idrequired | string | null | |
audiencerequired | object | |
includerequired | object | |
excluderequired | object | |
track_opensrequired | boolean | |
track_clicksrequired | boolean | |
scheduled_atrequired | string | null | |
send_started_atrequired | string | null | |
send_completed_atrequired | string | null | |
recipient_countrequired | integer | |
created_atrequired | string | |
updated_atrequired | string |
CampaignStats
Fields
objectrequired | "campaign_stats" | |
campaign_idrequired | string | |
sentrequired | integer | |
deliveredrequired | integer | |
openedrequired | integer | |
unique_openedrequired | integer | |
clickedrequired | integer | |
unique_clickedrequired | integer | |
bouncedrequired | integer | |
soft_bouncedrequired | integer | |
complainedrequired | integer | |
unsubscribedrequired | integer | |
failedrequired | integer | |
open_raterequired | number | null | |
click_raterequired | number | null | |
click_to_open_raterequired | number | null | |
bounce_raterequired | number | null | |
hard_bounce_raterequired | number | null | |
complaint_raterequired | number | null | |
unsubscribe_raterequired | number | null | |
updated_atrequired | string | null |
Contact
Fields
idrequired | string | |
objectrequired | "contact" | |
emailrequired | string | |
first_namerequired | string | null | |
last_namerequired | string | null | |
statusrequired | "subscribed" | "unsubscribed" | "bounced" | "complained" | "pending" | |
custom_fieldsrequired | object | |
opt_in_sourcerequired | string | null | |
opt_in_atrequired | string | null | |
last_engaged_atrequired | string | null | |
timezonerequired | string | null | |
created_atrequired | string | |
updated_atrequired | string |
CustomField
Fields
idrequired | string | |
objectrequired | "custom_field" | |
keyrequired | string | |
labelrequired | string | |
typerequired | "text" | "number" | "date" | "boolean" | |
created_atrequired | string | |
updated_atrequired | string |
Enrollment
Fields
objectrequired | "enrollment" | |
requestedrequired | integer | |
enrolledrequired | integer |
Error
Every 4xx and 5xx response has this shape, whatever went wrong.
Fields
errorrequired | object | |
typerequired | "invalid_request_error" | "authentication_error" | "permission_error" | "rate_limit_error" | "idempotency_error" | "api_error" | The coarse class. Switch on this, not on code. |
coderequired | "parameter_invalid" | "parameter_missing" | "malformed_request" | "api_key_missing" | "api_key_invalid" | "api_key_revoked" | "api_key_expired" | "insufficient_scope" | "resource_missing" | "resource_conflict" | "idempotency_key_in_flight" | "unprocessable" | "rate_limit_exceeded" | "internal_error" | The specific reason, for a human and for a log. |
messagerequired | string | |
param | string | The offending field, as a dotted path. Present when one field is at fault. |
request_idrequired | string | Also returned in X-Request-Id. Quote it in a support message and we can find exactly this request. |
List
Fields
idrequired | string | |
objectrequired | "list" | |
namerequired | string | |
descriptionrequired | string | null | |
created_atrequired | string | |
updated_atrequired | string |
MembershipChange
Fields
objectrequired | "membership_change" | |
changedrequired | integer |
Order
Fields
idrequired | string | |
objectrequired | "order" | |
external_idrequired | string | |
emailrequired | string | |
contact_idrequired | string | null | |
statusrequired | "paid" | "refunded" | "partially_refunded" | "cancelled" | |
total_centsrequired | integer | |
refunded_centsrequired | integer | |
currencyrequired | string | |
occurred_atrequired | string | |
sourcerequired | string | null | |
attributed_campaign_idrequired | string | null | |
attributed_automation_idrequired | string | null | |
attributed_node_idrequired | string | null | |
attribution_rulerequired | string | null | |
attributed_atrequired | string | null | |
created_atrequired | string | |
updated_atrequired | string |
Segment
Fields
idrequired | string | |
objectrequired | "segment" | |
namerequired | string | |
descriptionrequired | string | null | |
rulesrequired | object | The saved rule tree, in the grammar segmentRulesSchema defines. Pass it back unchanged to leave it alone. |
versionrequired | integer | |
matchrequired | "all" | "any" | |
groupsrequired | any[] | |
last_countrequired | integer | null | |
last_counted_atrequired | string | null | |
created_atrequired | string | |
updated_atrequired | string |
Tag
Fields
idrequired | string | |
objectrequired | "tag" | |
namerequired | string | |
colorrequired | "neutral" | "positive" | "warning" | "danger" | "info" | |
created_atrequired | string | |
updated_atrequired | string |
Webhook
A webhook endpoint subscription (URL + events). Not the body we POST to your URL — that is WebhookEvent.
Fields
idrequired | string | |
objectrequired | "webhook" | |
urlrequired | string | The HTTPS URL we POST events to. |
descriptionrequired | string | null | |
eventsrequired | ("email.delivered" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "contact.unsubscribed" | "campaign.finished")[] | Dotted event names this endpoint is subscribed to. |
statusrequired | "enabled" | "disabled_by_user" | "disabled_by_failure" | |
disabled_reasonrequired | string | null | |
consecutive_failuresrequired | integer | |
last_success_atrequired | string | null | |
last_failure_atrequired | string | null | |
secretrequired | string | null | Signing secret. Present only on the create/rotate response; null on every read afterwards. |
created_atrequired | string | |
updated_atrequired | string |
WebhookEvent
The JSON body we POST to your endpoint URL when a subscribed event happens. Includes the contact's email on message and opt-out events so you can match them to your own records. Not the same as the Webhook resource (the endpoint subscription).
Fields
idrequired | string | Stable source-event id. Delivery is at-least-once — key your idempotency on this. |
typerequired | "email.delivered" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "contact.unsubscribed" | "campaign.finished" | The dotted event name you subscribed to, e.g. email.opened. |
occurred_atrequired | string | When the event happened, not when we delivered it. |
organization_idrequired | string | Your organisation. |
datarequired | object | Message-event fields. For contact.unsubscribed, data is {contact_id, email, campaign_id?}. For campaign.finished, data is {campaign_id, name, sent, failed, total} — no contact fields. |
campaign_idrequired | string | The campaign that sent the message. |
contact_idrequired | string | Our id for the person. |
emailrequired | string | null | The contact's address — match this to a contact in your system without calling the API back. Null only if the contact was deleted between the event and the emit. |
message_recipient_idrequired | string | This specific send of the message. |
machinerequired | boolean | True when an open or click came from a scanner or link unfurler rather than a person. Always present on message events, including when false. Machine hits are excluded from every figure in the app. |
link_url | string | null | Clicks only — the URL that was clicked. |
bounce_type | string | null | Bounces only — permanent, transient, or undetermined. |
bounce_subtype | string | null | Bounces only — provider subtype when available. |
diagnostic_code | string | null | Bounces only — SMTP diagnostic when available. |
{
"id": "evt_01hxyz_email_opened",
"type": "email.opened",
"occurred_at": "2026-08-03T10:30:00.000Z",
"organization_id": "org_01hxyz",
"data": {
"campaign_id": "cmp_01hxyz",
"contact_id": "con_01hxyz",
"email": "[email protected]",
"message_recipient_id": "mr_01hxyz",
"machine": false
}
}Everything here is generated from the schemas the API actually validates against. The machine-readable form is at /api/v1/openapi.json.