Transactional email API for developers
Receipts, one-time codes, password resets, order updates. Same free allowance and same balance as your campaigns — and, importantly, the same suppression list, so an unsubscribe stops your API too.
Free: a test key at signup, then 200 live emails a month once you verify a domain (no credit card)Then: US$2 per 1,000, prepaidTaiwan accounts: 100 free, then NT$0.30 per email
curl -X POST https://edm.cresclab.com/api/email/send \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"to": "user@example.com",
"subject": "Your code: 483291",
"html": "<p>Code <b>483291</b></p>"
}'
Returns 202 { "id": "...", "status": "queued" }. Full spec: openapi.yaml.
Two key modes, told apart by the prefix. A key beginning sk_test_ records the message and returns 202 with "test_mode": true and "delivery": "skipped_test_mode" — it never hands anything to the mail provider, so nothing arrives and nothing is charged. Every new account starts with a test key. When you want mail to land, create an sk_live_ key on the Developers page and swap it in. Live keys are issued once the account has a verified sending domain (three DNS records, free on every plan), credit or a paid plan; until then creating one returns 403 live_key_needs_verification. The domain only proves the account: API mail still goes out from the shared address.
Sending the same email more than once? Store it once and send with an id — the bytes are identical every time, and {{variables}} (same syntax as campaign emails, {{name|fallback}} included) carry the per-message parts.
# once
curl -X POST https://edm.cresclab.com/api/templates \
-H "Authorization: Bearer sk_live_..." -H "Content-Type: application/json" \
-d '{"name":"order-confirmation","subject":"Order {{order_id}} confirmed",
"html":"<p>Hi {{first_name|there}}, order {{order_id}} is on its way.</p>"}'
# → 201 { "id": "7", "variables": ["order_id","first_name"], ... }
# every time
curl -X POST https://edm.cresclab.com/api/email/send \
-H "Authorization: Bearer sk_live_..." -H "Content-Type: application/json" \
-d '{"to":"user@example.com","template_id":"7","variables":{"order_id":"1042","first_name":"Ada"}}'
Same for agents: the MCP server has create_template / list_templates, and send_email takes template_id.
Delivery events — sent, delivered, opened, clicked, bounced, complained — are pushed to your endpoint as they happen, HMAC-signed in X-MAACGO-Signature and retried with backoff for about four hours (six tries). Failed deliveries are listed in the app with a retry button, so a webhook outage on your side is recoverable rather than lost.
Endpoints on private networks are refused. If you are testing locally, use a public tunnel rather than localhost.
There is an MCP server at https://edm.cresclab.com/api/mcp, so Claude Code, Cursor, VS Code, Windsurf, Claude Desktop and Codex can send mail, add contacts, run a campaign someone already wrote, and read the report back — 22 tools on the same key. In Claude Code it is one line:
claude mcp add --transport http maacgo-email https://edm.cresclab.com/api/mcp --header "Authorization: Bearer sk_test_..."
Cursor, VS Code and Windsurf take the same URL and header in their config file, with nothing to install; Claude Desktop and Codex reach it through the mcp-remote bridge (Node.js 18+). Snippets for every client → Scopes are deliberately tight: an agent can send, but cannot create a campaign, issue a key or add credit.
The same meter as campaigns: API messages count against your plan's included emails, and beyond that cost US$2 per 1,000 from a prepaid balance (Taiwan accounts: per email in NT$). One balance across both product lines. If we accept a message and the provider refuses it, the charge is returned automatically. See pricing.
/api/email/send returns 403 for that address. This is deliberate: on a shared sending domain, an API that bypassed unsubscribes would be an evasion channel paid for by everyone else's deliverability.sk_live_ key needs a verified sending domain (free), credit or a paid plan. The same step raises the daily cap to 5,000 with a verified domain or credit, or to the plan's monthly volume on a paid international plan (5,000 and up), shared with campaigns, so live API mail is never held to the 300 a day that brand-new accounts start with.sk_test_ never deliver and are never charged.from (or your brand name), and reply_to sends replies wherever you choose. Campaigns can send from your own verified domain.sk_test_. The call returns 202 with test_mode set to true and delivery set to skipped_test_mode, and the message is recorded but never handed to the mail provider, so nothing arrives and nothing is charged. Every new account starts with a test key. When you want mail to land, create an sk_live_ key on the Developers page: it is issued once the account has a verified sending domain (three DNS records, free on every plan), credit or a paid plan, and until then creating one returns 403 live_key_needs_verification. The domain only proves the account; API mail still goes out from the shared address. Your free monthly emails apply to live mail from then on.https://edm.cresclab.com/api/mcp takes your key as a Bearer token. Claude Code, Cursor, VS Code and Windsurf connect to it directly with nothing to install; Claude Desktop and Codex connect through the mcp-remote bridge, which needs Node.js 18 or later. Its 22 tools cover sending, templates, message status, contacts, campaigns and reports, and the balance. An agent can send, but cannot create a campaign, issue a key or add credit. Setup for each client is at https://edm.cresclab.com/mcp.suppressed. Unsubscribes, hard bounces and complaints from your campaigns bind the API too, so one suppression list covers both.X-MAACGO-Signature and retried with backoff. You can also look a message up with GET /api/email/{id}.