I needed my AI agents to receive email. Not send it — just receive. Sign up for a service, grab the verification code, move on. Every existing solution was either overkill (full mail server) or not programmatic enough (Mailinator with no API).
So I built myagentinbox. Here's exactly how it works under the hood.
The entire service runs on four Cloudflare primitives and one external service:
No VMs, no containers, no mail servers. The Worker handles both the HTTP API and the incoming email processing.
This was the key insight. Cloudflare Email Routing lets you set a catch-all rule that sends all incoming email for a domain to a Worker. No MX record management, no SMTP servers, no Postfix configuration. You literally toggle it on in the dashboard and point it at your Worker.
The Worker receives a ForwardableEmailMessage with the raw email as a ReadableStream. I parse it with postal-mime, which handles MIME decoding, attachments, and address parsing.
I needed TTL. Every inbox, every message, every message list needs to disappear after 24 hours without a cron job or cleanup script. Redis has native key expiry. Upstash Redis works over HTTP, which is the only option in Cloudflare Workers (no TCP connections).
The data model is simple:
inbox:{address} → JSON (inbox metadata) TTL: 24h
msg:{id} → JSON (full message) TTL: remaining inbox TTL
inbox:{address}:messages → Sorted Set (summaries) TTL: remaining inbox TTL
One subtlety: when a new message arrives, I check the remaining TTL of the inbox key and use that for the message. So if an email arrives at hour 23, the message gets a 1-hour TTL — not a fresh 24 hours. Everything expires together.
Email attachments go into R2. I set a 1-day object lifecycle rule on the bucket, so attachments auto-delete without any cleanup logic. The key structure is {address}/{messageId}/{filename}.
Workers have a built-in rate limiting binding. You declare it in wrangler.toml:
[[ratelimits]]
name = "INBOX_LIMITER"
namespace_id = "1001"
simple = { limit = 3, period = 60 }
[[ratelimits]]
name = "READ_LIMITER"
namespace_id = "1002"
simple = { limit = 20, period = 60 }
Then in your Worker, it's one line to check: const { success } = await env.INBOX_LIMITER.limit({ key: clientIp }). No external rate limiting service, no Redis-based rate limiter. It runs at the edge.
When an email arrives, the Worker:
message.setReject() and the sender gets a bouncepostal-mimeThe important detail: if someone sends email to an address that was never created or has expired, the Worker rejects it immediately. No data is stored, no resources are wasted.
MCP (Model Context Protocol) lets AI agents discover and use tools. I added an MCP server at /mcp using the @modelcontextprotocol/sdk. It exposes four tools:
create_inbox — creates a disposable addresscheck_inbox — lists messages in an inboxread_message — returns the full message contentdownload_attachment — returns attachments (images as base64, text inline, binary as download URLs)The MCP tools call the same Redis/R2 functions as the REST API — no HTTP hop. Each tool has its own rate limiting and annotations (readOnlyHint, destructiveHint, etc.) so clients know what to expect.
An agent using Claude or Cursor can add myagentinbox as an MCP server and immediately start creating inboxes and reading emails as part of its workflow.
Each inbox gets a random 10-character alphanumeric prefix: a7k2m9x4bp@myagentinbox.com. That's 36^10 ≈ 3.6 trillion possible addresses — enough that guessing a valid inbox is impractical. No sequential IDs, no UUIDs that leak timing information.
A single Cloudflare Worker handles everything:
export default {
fetch: api.fetch, // HTTP API + MCP server
async email(message) { // Incoming email handler
await handleEmail(message, env, ctx);
},
};
The fetch handler is a Hono app serving the REST API, MCP endpoint, landing page, and static pages. The email handler processes incoming mail. Both share the same Redis and R2 bindings.
At low-to-moderate traffic:
The entire service runs for about $10/year until it hits meaningful scale.
If I were building this for higher scale, I'd add webhook support. Right now agents poll check_inbox every few seconds waiting for an email to arrive. A webhook that fires when an email lands would eliminate that polling entirely — better for agents, cheaper on infrastructure, and opens up a natural paid tier.
I'd also consider edge caching for inbox lookups. Every API call currently hits Redis to verify the inbox exists. A short-lived cache at the edge would cut those commands significantly.
The service is live at myagentinbox.com. Create an inbox in one click, or connect your agent via MCP. The full API is documented on the landing page.