Documentation

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.

POST /v1/messages
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.

Request body
{
  "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.

Successful response
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.

Example event
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.

Error body
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
Node.js
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.

From the guides

Reference deep-dives.

Standards, patterns, and edge cases that turn a working integration into a production one.

All guides →