SendRaven

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.

RequestPOST /v1/emails
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"
}
Response202 Accepted
{
  "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, with ok: false on 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/suppressionsOne 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-endpointsOne 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.

Three calls. Then everything else is the same key.

Start free, with inbound included and no card. Or read the sending docs first. When the reply comes back, it comes back as a thread.