FutureMail is email hosting with an API. Everything the web inbox can do, your code and your AI agents can do over REST, the MCP server, or a Resend-compatible API. Domains, addresses, sending, receiving, searching, threads, webhooks: same data, same permissions.
- API base URL:
https://api.futuremail.dev - MCP server (Streamable HTTP):
https://api.futuremail.dev/mcp - Resend-compatible API:
https://api.futuremail.dev/resend - Auth:
Authorization: Bearer fm_live_…
Quickstart
- Sign up. You get a free
you@futuremail.devaddress instantly, with no DNS to configure. - Create an API key in Settings → API keys.
- Send an email:
curl -X POST https://api.futuremail.dev/v1/emails \
-H "Authorization: Bearer $FUTUREMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "you@futuremail.dev",
"to": "delivered@test.futuremail.dev",
"subject": "Hello from FutureMail",
"text": "It works."
}'
delivered@test.futuremail.dev is a test address: it simulates a delivery without sending real mail and doesn't count toward your limits.
Authentication
Every request carries an API key: Authorization: Bearer fm_live_…. Keys belong to an organization (your personal org or a team) and only see that org's data.
| Scope | Allows |
|---|---|
full |
Everything, including domains, addresses, keys and webhooks |
send |
Sending, replying, forwarding, drafts |
read |
Listing, reading, searching, waiting, updating thread state |
Keys can be pinned to one mailbox (agent keys always are), limited to one sending domain, and given an expiry. Keys never manage the org itself (members, invites, billing); that needs a signed-in member.
Sending email
POST /v1/emails
| Field | Notes |
|---|---|
from |
An address you own in FutureMail |
to, cc?, bcc?, replyTo? |
"ada@x.com", "Ada <ada@x.com>", { address, name }, or an array of these |
subject |
Required unless a template provides it |
text?, html? |
Plain text and/or HTML body |
attachments? |
[{ filename, content (base64) | path (public URL), contentType?, contentId? }]. contentId makes it inline (<img src="cid:logo">). Max 20 attachments, 10 MB per message |
headers? |
Custom headers such as List-Unsubscribe |
tags? |
[{ name, value }], returned on the email, in webhooks, and filterable |
scheduledAt? |
ISO 8601, up to 30 days ahead. Reschedule with PATCH /v1/emails/:id, cancel with POST /v1/emails/:id/cancel |
template? |
{ id, variables }: render a published template (id or alias) |
- Batch:
POST /v1/emails/batchsends up to 100 emails in one call, validated all-or-nothing. - Idempotency: send an
Idempotency-Keyheader (≤256 chars). Retrying with the same key within 24 hours returns the original response instead of sending twice. - Delivery timeline: an email moves
queued→sent→delivered, or endsbounced/complained/failed.GET /v1/emails/:id/eventsreturns every event, includingopenedandclickedwhen tracking is on. - Replies and forwards:
POST /v1/emails/:id/reply({ text?, html?, replyAll? }) threads correctly and quotes the original;POST /v1/emails/:id/forward({ to, text? }) includes attachments.
Test addresses
Send to <outcome>[+label]@test.futuremail.dev to simulate an outcome without sending anything:
| Address | Result |
|---|---|
delivered@test.futuremail.dev |
delivered |
bounced@test.futuremail.dev |
bounced (permanent) |
complained@test.futuremail.dev |
delivered, then complained |
delayed@test.futuremail.dev |
delivery_delayed, then delivered |
Test sends don't count toward limits and can't be mixed with real recipients in one email.
Agent inboxes
Give every AI agent its own mailbox, address and API key:
curl -X POST https://api.futuremail.dev/v1/agents \
-H "Authorization: Bearer $FUTUREMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Signup bot" }'
# → { "mailbox": {…}, "address": {…}, "apiKey": { "id": "…", "key": "fm_live_…", … } }
# apiKey.key is shown once: store it now
The agent's key is pinned to its mailbox: it can only read and send from its own inbox.
Waiting for an email (OTP and magic links)
GET /v1/emails/wait long-polls until a matching inbound email arrives. Filter with from, to, subject and since; timeout is in seconds (≤300). It returns the email, or 204 on timeout. One-time codes and links are extracted for you:
curl "https://api.futuremail.dev/v1/emails/wait?from=github.com&timeout=120" \
-H "Authorization: Bearer $AGENT_KEY"
# → { …, "extracted": { "otp": "481516", "links": ["https://github.com/…"] } }
Reading and searching
GET /v1/threads?folder=inboxlists conversations (inbox,starred,sent,all,spam,trash);GET /v1/threads/:idreturns a thread with all its messages;PATCH /v1/threads/:idarchives, stars, trashes or marks read.GET /v1/emails?q=…searches messages with Gmail-style operators:from:to:subject:has:attachmentis:unreadis:starredin:inbox|sent|spam|trashbefore:2026-10-01after:2026-09-01newer_than:7dolder_than:1m. Quote values with spaces:subject:"weekly report".GET /v1/emails/:id/rawreturns the RFC 822 source;GET /v1/attachments/:iddownloads an attachment.
MCP server
FutureMail runs a remote MCP server over Streamable HTTP. Add it to any MCP client (Claude, Claude Code, Cursor, …):
{
"mcpServers": {
"futuremail": {
"type": "http",
"url": "https://api.futuremail.dev/mcp",
"headers": { "Authorization": "Bearer fm_live_…" }
}
}
}
With Claude Code:
claude mcp add --transport http futuremail https://api.futuremail.dev/mcp \
--header "Authorization: Bearer fm_live_…"
Tools: whoami, list_inbox, search_emails, read_thread, read_email, send_email, send_batch, get_email_events, cancel_scheduled_email, reschedule_email, list_templates, reply_to_email, forward_email, update_thread, wait_for_email, list_domains, add_domain, verify_domain, create_address, create_agent_inbox.
Use an agent key to confine the model to one mailbox.
Resend compatibility
FutureMail serves the same API in Resend's format under https://api.futuremail.dev/resend. Request bodies, responses and errors match Resend's, so moving over is a base URL and key change:
RESEND_BASE_URL=https://api.futuremail.dev/resend
RESEND_API_KEY=fm_live_…
import { Resend } from "resend";
const resend = new Resend("fm_live_…", { baseUrl: "https://api.futuremail.dev/resend" });
await resend.emails.send({
from: "Acme <hello@acme.com>", // an address you own in FutureMail
to: ["delivered@test.futuremail.dev"],
subject: "Hello",
html: "<p>It works</p>",
});
Supported: POST /emails, POST /emails/batch, GET /emails, GET /emails/:id, PATCH /emails/:id, POST /emails/:id/cancel, domains and API keys. Other Resend SDKs (Python, Go, PHP, Ruby, …) work if they let you override the base URL. Webhooks use FutureMail's signature format (below), not Svix's.
Webhooks
Create webhooks in Settings → Developers or with POST /v1/webhooks ({ url, events }; the signing secret is returned once).
Events: email.received, email.scheduled, email.sent, email.delivery_delayed, email.delivered, email.bounced, email.complained, email.failed, email.suppressed, email.opened, email.clicked, domain.verified.
Payload: { "id": "evt_…", "type": "email.delivered", "createdAt": "…", "data": { … } }. The id is stable across retries, so use it to dedupe.
Every delivery is signed: FutureMail-Signature: t=<unix>,v1=<hex> where v1 = HMAC_SHA256(secret, "<t>.<raw body>"). Reject requests whose t is more than 5 minutes old:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyFutureMail(rawBody: string, header: string, secret: string) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=") as [string, string]));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const received = Buffer.from(parts.v1 ?? "", "hex");
return received.length === expected.length / 2 && timingSafeEqual(received, Buffer.from(expected, "hex"));
}
Non-2xx responses and timeouts (10s) are retried, up to 8 attempts over about 28 hours. Replay any delivery with POST /v1/webhooks/:id/deliveries/:deliveryId/replay. For a live feed instead, GET /v1/events is a Server-Sent Events stream of the same events.
Domains and addresses
On Pro you can use any domain you own:
POST /v1/domainswith{ "name": "acme.com" }returns the DNSrecords[]to add: DKIM (three CNAMEs), MX for receiving, a custom MAIL FROM (MX + SPF TXT) and a recommended DMARC record.- Add them at your DNS provider, then
POST /v1/domains/acme.com/verify. - Create addresses:
POST /v1/addresseswith{ "address": "hello@acme.com" }. Addresses are free and unlimited; set a catch-all per domain, and plus-addressing (me+tag@acme.com) always works.
Per-domain settings (PATCH /v1/domains/:idOrName): catchAllMailboxId, mailFromSubdomain, tlsPolicy, openTracking, clickTracking, trackingSubdomain.
Templates and suppressions
- Templates:
POST /v1/templateswith{ name, alias?, subject, html?, text?, variables? }, thenPOST /v1/templates/:idOrAlias/publish. Placeholders are{{name}}(HTML-escaped) and{{{name}}}(raw). Send withtemplate: { id, variables }. - Suppressions: permanent bounces and spam complaints are suppressed automatically. Manage the list with
GET/POST /v1/suppressions,DELETE /v1/suppressions/:email, andPOST /v1/suppressions/check.
Endpoint reference
| Endpoint | Purpose |
|---|---|
POST /v1/emails, POST /v1/emails/batch |
Send one or up to 100 emails |
GET /v1/emails, GET /v1/emails/:id |
List/search and read messages |
GET /v1/emails/wait |
Long-poll for the next matching inbound email |
GET /v1/emails/:id/events |
Delivery timeline |
PATCH /v1/emails/:id, POST /v1/emails/:id/cancel |
Reschedule or cancel a scheduled email |
POST /v1/emails/:id/reply, POST /v1/emails/:id/forward |
Reply in-thread, forward |
GET/PATCH/DELETE /v1/threads/:id, POST /v1/threads/batch |
Conversations |
GET/POST /v1/domains, POST /v1/domains/:idOrName/verify |
Custom domains and DNS |
GET/POST /v1/addresses, GET /v1/addresses/availability |
Addresses and aliases |
GET/POST /v1/mailboxes, POST /v1/agents |
Mailboxes and agent inboxes |
GET/POST /v1/templates, POST /v1/templates/:id/publish |
Templates |
GET/POST /v1/suppressions |
Suppression list |
GET/POST /v1/api-keys |
API keys |
GET/POST /v1/webhooks, GET /v1/events |
Webhooks and the SSE event stream |
GET /v1/me |
Current org, role, plan and usage |
Errors, pagination and rate limits
- Errors:
{ "error": { "code": "not_found", "message": "Email not found" } }with a matching HTTP status. Paid-plan actions on Free return402 upgrade_required. - Pagination: lists return
{ "data": [...], "nextCursor": "…" | null }and acceptlimit(1–100) andcursor. - Rate limits: 10 requests/second per API key. Responses carry
RateLimit-Limit,RateLimit-RemainingandRateLimit-Reset; over the limit you get429withRetry-After, and the request was not processed, so it is safe to retry.
Plans and limits
| Free | Pro ($20/month) | |
|---|---|---|
| Addresses on futuremail.dev | ✓ | ✓ |
| Agent inboxes, API, MCP, webhooks | ✓ | ✓ |
| Custom domains and addresses on them | – | Unlimited |
| Recipients per month | 3,000 (100 a day) | 50,000, no daily cap |
Limits are hard caps: you're never charged overage, and there's no per-seat pricing. At most 50 recipients and 10 MB per message.