Ship Transactional Email With A Single Endpoint.
A single API key, one endpoint, and honest response bodies you can debug from a terminal. Everything below is what MailRoundup actually does, no roadmap items dressed up as features.
Quickstart
Send a verification code with a single HTTP request. Replace $MR_API_KEY with the key from your dashboard.
curl https://api.mailroundup.com/v1/messages \
-H "Authorization: Bearer $MR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "ada@customer.io",
"template": "verify-code",
"data": { "code": "418205" }
}'
# → 202 Accepted { "id": "msg_01HZKQ6R8P", "status": "queued" }You will get back a msg_ id you can use to look the message up in the dashboard or the delivery logs.
Authentication
Every request is authenticated with a bearer token in the Authorization header. Keys are created in the dashboard and scoped per environment. Never ship a live key in browser code.
Authorization: Bearer mr_live_1a2b3c4d5e6f...
Rotate a key at any time from the dashboard. The old key keeps working for 24 hours so you can roll a deploy without dropping mail.
Send A Message
One endpoint, POST /v1/messages, handles every transactional send. Provide a recipient, a template, and the data the template needs.
{
"to": "ada@customer.io",
"template": "password-reset",
"data": { "name": "Ada", "reset_url": "https://..." },
"idempotency_key": "reset-9f2c1b"
}Pass an idempotency_key on any send you might retry. A repeat request with the same key returns the original message id instead of sending a second copy.
202 Accepted
{
"id": "msg_01HZKQ6R8P",
"status": "queued",
"to": "ada@customer.io"
}Templates
Templates live in your dashboard and are referenced by slug. Every template ships with a plain-text fallback and a brand-mark header, so you do not have to hand-write MIME parts. Common slugs your account starts with:
- verify-code — one time codes with expiry line
- password-reset — reset link with expiry
- receipt — itemized purchase receipt
- account-alert — security and account change notices
Edit copy, add new templates, and preview against real data from the dashboard.
Webhooks
Register a webhook URL and MailRoundup will POST a signed event every time a message changes state. Events include message.accepted, message.delivered, message.bounced, message.complained, and message.opened.
POST https://your-app.com/webhooks/mailroundup
X-MailRoundup-Signature: t=1735689600,v1=a3f1...
{
"type": "message.delivered",
"message_id": "msg_01HZKQ6R8P",
"to": "ada@customer.io",
"occurred_at":"2026-01-15T18:04:22Z"
}Verify the signature against your webhook secret before trusting the payload. We retry failed deliveries with exponential backoff for up to 24 hours.
Delivery Logs
Every attempt, every response from the receiving server, is searchable in the dashboard by recipient, template, or message id. Log history depends on your plan (14 days on Starter, 90 days on Growth, 12 months on Scale). Export any filtered view to CSV in one click.
Programmatic access is available at GET /v1/messages with query filters for recipient, template, status, and time range.
Suppression
Hard bounces and complaints are added to your suppression list automatically the moment they arrive. A suppressed address will not receive further mail, and sends to a suppressed address resolve with status: "suppressed" rather than being delivered.
View, export, or manually manage the suppression list from the dashboard. This is a strict policy: it is what keeps your sender score where it belongs.
Rate Limits
The default rate limit is 100 requests per second per API key. Every response includes:
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 87 X-RateLimit-Reset: 1735689660
When you exceed the limit you get 429 Too Many Requests with a Retry-After header. Growth and Scale plans get higher default limits. Talk to us if you need more.
Errors
Errors return standard HTTP status codes and a JSON body with a machine-readable code and a human-readable message.
400 Bad Request
{
"code": "invalid_recipient",
"message": "The 'to' field must be a valid email address.",
"field": "to"
}Common codes:
- invalid_recipient — the address failed validation
- template_not_found — no template matches that slug
- recipient_suppressed — the address is on your suppression list
- domain_not_verified — the sending domain still needs DNS records
- rate_limited — you hit the per-second cap
Client Libraries
Official libraries wrap the HTTP API in the languages our customers actually ship. Each one exposes the same call shape and returns the same message ids you would see from curl.
- Node.js — npm install mailroundup
- Python — pip install mailroundup
- Ruby — gem install mailroundup
- Go — go get github.com/mailroundup/mailroundup-go
import MailRoundup from "mailroundup";
const mr = new MailRoundup(process.env.MR_API_KEY);
await mr.messages.send({
to: "ada@customer.io",
template: "verify-code",
data: { code: "418205" },
});Need Help Getting Started?
Our team helps with domain setup, template design, and any deliverability question you can throw at us.
Reference deep-dives.
Standards, patterns, and edge cases that turn a working integration into a production one.
- February 11, 2026
SPF, DKIM, And DMARC Explained For Developers
A practical, RFC-grounded walkthrough of the three email authentication standards every transactional sender has to get right.
Read guide → - April 1, 2026
Webhooks For Transactional Email: Delivered, Bounced, Complained
How to consume email event webhooks reliably: signature verification, replay protection, idempotent handlers, and the events that actually matter.
Read guide → - March 18, 2026
Idempotency For Transactional Email APIs
How to make sure a retried API call never sends a duplicate receipt or a second password reset: the idempotency key pattern applied to email.
Read guide →