Send your first email in two minutes
A typed SDK, a clean REST API, and a first-class MCP server — all sending over Amazon SES. Everything below is copy-paste-correct and exactly what your agent reads.
Quickstart
Install the SDK, drop in your key, and send. The same call handles transactional and broadcast email — there's no second product to learn.
npm install @macromail/sdkimport { MacroMail } from '@macromail/sdk';
const mm = new MacroMail(process.env.MACROMAIL_API_KEY);
const { data, error } = await mm.emails.send({
from: 'You <hello@yourdomain.com>',
to: ['user@example.com'],
subject: 'Welcome to MacroMail',
html: '<p>It works — sent over Amazon SES.</p>',
});A successful send returns { data, error } with a status of queued — not delivered. Delivery is confirmed asynchronously via webhooks and the logs.
Authentication
Every request authenticates with a secret API key passed as a bearer token:
Send real email to real inboxes over Amazon SES. Treat them like passwords — server-side only, never in client bundles.
Run the full validation and queueing pipeline and get back a real id — but nothing is ever delivered. Perfect for CI.
Scopes
Keys carry one of two scopes. A full key can manage domains, audiences, and other keys; a sending key can only send email. Give production services the narrowest scope they need.
import { MacroMail } from '@macromail/sdk';
// Pass your secret key. Live keys send real email; test keys
// run the full pipeline but never hit a real inbox.
const mm = new MacroMail('mm_live_xxxxxxxxxxxxxxxxxxxx');
// Or over the wire — every request is authenticated with a
// bearer token in the Authorization header:
// Authorization: Bearer mm_live_xxxxxxxxxxxxxxxxxxxxSending email
mm.emails.send() (or POST /v1/emails) takes the fields below and returns { data, error }. It never throws on an expected failure — inspect error instead. A successful call returns status queued; delivery is confirmed later.
fromrequired- string
Sender address. Use a display name + a verified domain, e.g. "Acme <hello@acme.com>".
torequired- string | string[]
One or more recipient addresses. Up to 50 per send.
subjectrequired- string
The email subject line.
htmloptional- string
HTML body. Provide html, text, or both — at least one is required.
textoptional- string
Plain-text body. Sent as the text/plain alternative.
ccoptional- string | string[]
Carbon-copy recipients.
bccoptional- string | string[]
Blind carbon-copy recipients.
reply_tooptional- string | string[]
Address replies should be routed to. Defaults to the from address.
tagsoptional- { name, value }[]
Key/value labels for filtering logs, analytics, and webhooks.
scheduled_atoptional- string (ISO 8601)
Schedule the send for a future time, e.g. "2026-07-01T09:00:00Z". Returns status scheduled.
Domains & deliverability
Add a domain with POST /v1/domainsand we return the exact records to publish at your DNS provider. We check them live — there's no vanity score. Once all five are green, you can send.
| Type | Host / Name | Value | Purpose |
|---|---|---|---|
| TXT | @ | v=spf1 include:amazonses.com ~all | SPF — authorizes Amazon SES to send for your domain. |
| CNAME | <token1>._domainkey.<domain> | <token1>.dkim.macromail.dev | DKIM key 1 of 3 — signs your mail so it isn't forged. |
| CNAME | <token2>._domainkey.<domain> | <token2>.dkim.macromail.dev | DKIM key 2 of 3. |
| CNAME | <token3>._domainkey.<domain> | <token3>.dkim.macromail.dev | DKIM key 3 of 3. |
| TXT | _dmarc.<domain> | v=DMARC1; p=none; rua=mailto:dmarc@macromail.dev | DMARC — start in monitor mode (p=none), tighten once aligned. |
The three DKIM tokens are unique per domain and shown in your dashboard when you add it. Keep DMARC at p=none until your reports look clean, then move to quarantine and reject.
API reference
A small, predictable REST surface. Base URL https://api.macromail.dev. Every response is JSON and every endpoint is authenticated.
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /v1/emails | Send a single transactional email. |
| GET | /v1/emails | List recent emails with their delivery status. |
| GET | /v1/emails/:id | Retrieve one email and its full lifecycle timeline. |
| POST | /v1/emails/batch | Send up to 100 emails in a single request. |
| POST | /v1/domains | Add a sending domain and get its DNS records. |
| POST | /v1/domains/:id/verify | Trigger a live re-check of a domain's DNS. |
| POST | /v1/api-keys | Create a scoped API key (full or sending-only). |
| POST | /v1/audiences | Create an audience to hold contacts. |
| POST | /v1/contacts | Add or upsert a contact in an audience. |
| POST | /v1/broadcasts | Send a broadcast to an audience. |
| POST | /v1/webhooks | Register an endpoint for signed delivery events. |
Example — send an email
A send returns the new email's id and a queued status immediately.
// Request — POST /v1/emails
{
"from": "Acme <hello@acme.com>",
"to": ["user@example.com"],
"subject": "Welcome to Acme",
"html": "<p>You're in.</p>"
}
// Response — 200 OK
{
"id": "em_2b1f9c7a4d3e",
"status": "queued",
"created_at": "2026-06-28T17:04:11Z"
}Errors
Errors come back in the error half of { data, error } with a stable name, an HTTP status, and a human-readable message. Branch on the name, not the prose.
| Code | Status | Cause | Fix |
|---|---|---|---|
authentication_error | 401 | Missing, malformed, or revoked API key. | Send Authorization: Bearer <key>. Rotate the key if it was leaked. |
permission_error | 403 | The key's scope doesn't allow this action. | Use a key with the full scope, or call a sending-only endpoint. |
domain_not_verified | 422 | The from domain hasn't passed DNS verification. | Add the SPF/DKIM/DMARC records, then POST /v1/domains/:id/verify. |
validation_error | 422 | A field is missing or malformed (bad address, no body). | Read error.message — it names the exact field that failed. |
rate_limit_exceeded | 429 | Too many requests in the current window. | Back off using the Retry-After header; batch where you can. |
not_found | 404 | No resource with that id, or it isn't on your account. | Check the id and that the key belongs to the right project. |
Webhooks
Register an endpoint with POST /v1/webhooks and we'll POST a JSON event for each delivery milestone. Every payload is signed with your webhook secret so you can verify it came from us, and failed deliveries are retried with exponential backoff.
- email.deliveredThe receiving server accepted the message.
- email.bouncedPermanent failure — the address is undeliverable.
- email.complainedThe recipient marked the message as spam.
- email.openedThe recipient opened the email (when tracking is on).
- email.clickedA tracked link in the email was clicked.
Verify the MacroMail-Signature header against the raw request body before trusting an event. Return a 2xx quickly; anything else is treated as a failure and retried.
Agents / MCP
MacroMail ships a first-class MCP server. Connect it to Claude, Cursor, or Windsurf and your agent can create API keys, verify domains, and send test email without leaving the editor. Install it in one line:
npx @macromail/mcp install