Simple
One key. One suppression list. One event stream.
Transactional mail, campaigns, automations, templates, contacts and inbound replies are one API behind one credential.
There is no second product to sign up for, no second dashboard, and no second list of people who asked you to stop.
Idempotency-Key: 7f3e9c…
{
"from": "Acme <hi@mail.acme.com>",
"to": "maya@acme.com",
"subject": "Welcome",
"html": "<p>Hi Maya</p>",
"scheduled_at": "in 3 days"
}{
"id": "msg_8a1…",
"status": "scheduled",
"thread_id": null,
"scheduled_at": "2026-09-05T09:14:00Z"
}Three calls
Add a domain, check it verified, send.
Adding acme.com creates two sending identities, mail.acme.com for transactional and news.acme.com for campaigns, and hands back the DNS records to publish. Verification shows each record beside what is resolving, so a stuck domain tells you which record is wrong rather than just failing.
Add your domain
POST /v1/domains { "domain": "acme.com" } 201 { "data": [ { "domain": "mail.acme.com", "risk_class": "transactional", "status": "pending", "dns_records": [ … ] }, { "domain": "news.acme.com", "risk_class": "marketing", … } ] }Check it verified
POST /v1/domains/:id/verify 200 { "status": "verified", "dns_records": [ { "kind": "dkim", "type": "CNAME", "value": "…", "observed": "…", "ok": true }, … ] }Or
pending, withok: falseon exactly the record that is still missing.Send
POST /v1/emails Idempotency-Key: 7f3e9c… { "from": "Acme <hi@mail.acme.com>", "to": "maya@acme.com", "subject": "Welcome", "html": "<p>Hi Maya</p>" } 202 { "id": "msg_8a1…", "status": "sent" }
- emails
- batch of 100
- scheduling in plain words
- templates
- contacts
- audiences
- segments
- broadcasts
- automations
- topics
- inbound
- threads
- approvals
- webhooks
- usage
- one suppression list
Scheduling and batches
“In 3 days” is a valid timestamp. A bad one is an error.
Scheduling in plain words
"scheduled_at": "in 2 min"
"scheduled_at": "in 3 days"
"scheduled_at": "in 2 weeks"
"scheduled_at": "2026-09-05T09:00:00Z"
"scheduled_at": "next tuesday"
→ 422 { "error": { "type": "invalid_schedule" } }Welcome sequences are written in that vocabulary, so the API keeps accepting it. Anything it cannot parse is refused rather than sent now: a silent fallback to “now” would fire every step of a sequence at once.
While a message is still scheduled, DELETE /v1/emails/:id cancels it.
A batch of 100, each on its own
POST /v1/emails/batch
[ { "to": "maya@acme.com", … },
{ "to": "not-an-address", … },
… up to 100 ]
207 {
"object": "list",
"failed": 1,
"data": [
{ "ok": true, "id": "msg_8a1…", "status": "sent" },
{ "ok": false, "error": {
"type": "invalid_request",
"message": "…" } },
…
]
}Each entry succeeds or fails independently and results keep input order, so you retry only the failures. 202 means all fine; 207 means some failed and failed says how many.
One bad address aborting the other ninety-nine would be far worse for an agent looping over a list.
Once, and only once
Send it twice. One email arrives.
Idempotency-Key: <uuid>
Replaying the same key within 24 hours returns the stored response instead of acting again, marked Idempotent-Replay: true.
It matters more here than in most APIs, because the duplicate lands in someone’s inbox.
GET /v1/suppressions
One list of who you must not mail and why: hard_bounce, complaint, unsubscribe, manual, list_hygiene. Every send checks it, whichever half of the product the send came from.
A send to a suppressed address returns 202 with skipped: true and the reason, so support can answer why it did not arrive.
POST /v1/webhook-endpoints
One stream of delivery, bounce, complaint, open, click and inbound, signed with a secret shown once.
Transactional and campaign events arrive on the same endpoint in the same shape. Webhooks.
Elsewhere
Transactional and marketing are usually two products.
Resend sells marketing as a separate plan; most of the field does the same, or ships one half and leaves the other out.
Two products means two of everything, and the duplicates are where the failures live.
Two suppression lists that disagree.
Someone unsubscribes from the newsletter on one vendor and keeps bouncing on the other, or the reverse, and nobody notices until a complaint does.
Two dashboards, two logs.
“Why did this not arrive?” is answered by checking both, and the reply to a campaign lands in neither.
Two meters, one of them per contact.
The marketing half is priced by list size, so the bill grows with the database rather than with what you send. Here it is one meter.
Questions
Can one API send both transactional and marketing email?
Yes, on one key, one suppression list and one event stream — but on two sending identities. Transactional goes out on mail. and marketing on news., with separate configuration sets, so a complaint spike on a newsletter can never stop a password reset.
What is the difference between a transactional and a marketing email API?
Usually that they are two products with two bills, two suppression lists and two sets of credentials to keep in sync. Here the difference is a riskClass on the send: the routing, the identity and the reputation are separate, and everything you integrate against is not.

Three calls. Then everything else is the same key.
Start free, with inbound included and no charge until you choose paid sending. Or read the sending docs first. When the reply comes back, it comes back as a thread.

