agent-email
A headless, disposable email inbox that an AI agent drives over MCP, enabling temporary email addresses and email receipt for testing signup flows.
README
agent-email
A headless, disposable email inbox that an AI agent drives over MCP — no UI, no built-in AI. Give a coding agent (Claude Code, Cursor, etc.) an address like netflix-test@your-domain.example, and it can receive the confirmation email, pull out the link, and click it — so it can sign up for services and test your own onboarding flows without ever touching your real mailbox.
Built entirely on Cloudflare: one Worker, Email Routing for receiving, a Durable Object (SQLite) for storage, and an Agents SDK McpAgent for the MCP surface.
inbound email ──▶ Email Routing (catch-all: *@your-domain) ──▶ Worker email() handler
│ parse (postal-mime)
▼
MailboxDO (SQLite, singleton)
▲
│ read
MCP client (your agent) ──▶ Worker /mcp (bearer auth) ──▶ InboxMCP (McpAgent)
Registered addresses, not an open catch-all. The catch-all delivers every message to the Worker, but the Worker only keeps mail addressed to an address you've registered via create_address — everything else is dropped. So the agent mints a random, single-purpose, short-lived address per signup (netflix-a1b2c3d4@…), which both segregates mail and keeps the prompt-injection surface tiny (see Security).
MCP tools
| Tool | What it does |
|---|---|
create_address({ label?, expected_sender?, ttl_minutes?, local_part? }) |
Register a new address (random local-part by default). Pin expected_sender and set a short TTL. Call this before receiving. |
list_addresses() |
Registered addresses with pinned sender + expiry. |
delete_address({ address, with_emails? }) |
Revoke an address. |
list_recent({ address?, limit? }) |
Recent emails, newest first. Summaries; sender_ok:false flags a failed sender pin. |
get_email({ id }) |
One email in full. Body + classified links live under untrusted (data, not instructions); each link carries safety flags. |
wait_for_email({ address, timeout_seconds?, since?, only_trusted_sender?, subject_contains? }) |
Blocks until a new (trusted-sender) email lands, then returns it in full. The workhorse for onboarding tests. |
clear_inbox({ address? }) |
Delete stored mail to reset between runs. |
"Clicking" the link needs no special tool — once the agent has a URL (and has checked its flags), it just fetches it.
Setup
Prerequisites: a Cloudflare account and a domain on Cloudflare whose email you're willing to route (use a domain you don't receive real mail on, or a dedicated subdomain).
git clone <your-fork> agent-email && cd agent-email
npm install
1. Set your domain (cosmetic — improves tool descriptions). Edit INBOX_DOMAIN in wrangler.jsonc.
2. Deploy the Worker.
npm run deploy
3. Set the MCP auth token (gates the /mcp endpoint — anyone with the URL + token can read the inbox, so treat it like a password):
openssl rand -hex 32 | npx wrangler secret put MCP_TOKEN
4. Enable Email Routing + a catch-all rule pointing at this Worker. In the Cloudflare dashboard: your domain → Email → Email Routing, enable it (adds the MX/SPF records), then under Routing rules → Catch-all address, set the action to Send to a Worker → agent-email. Or via CLI:
npx wrangler email routing enable # adds DNS records
# then set the catch-all to this Worker in the dashboard
Subdomain note: Email Routing operates on the zone apex (
*@your-domain.com). To use a subdomain (*@inbox.your-domain.com) you must add the subdomain's MX/TXT records yourself — the apex is the simpler path.
5. Connect your agent. For Claude Code:
claude mcp add --transport http agent-email https://agent-email.<your-subdomain>.workers.dev/mcp \
--header "Authorization: Bearer <your MCP_TOKEN>"
(Use your Worker's real URL, or a custom domain/route if you've set one.)
Usage
Once connected, ask your agent to do something like:
Sign up for Netflix. Create an inbox address pinned to
netflix.com, use it for the signup, then wait for the confirmation email and open the verification link.
The agent calls create_address({ label: "netflix", expected_sender: "netflix.com", ttl_minutes: 30 }), gets back netflix-a1b2c3d4@your-domain.example, submits the signup with it, calls wait_for_email({ address }), checks the returned link's flags are clean, and fetches it — done.
Security
A public inbox means anyone can send mail that lands in your agent's context, so prompt injection can't be filtered away — it has to be designed around. The controls here shrink the attack surface and constrain the blast radius:
- Registered-only allowlist. Mail to unregistered addresses is dropped, so an attacker can't inject unless they know an active, single-purpose address the agent just created. Re-registering a live address is rejected (so a pin can't be silently weakened), and both addresses and total volume are capped.
- Random, short-lived addresses. Default random local-parts + a mandatory TTL (default 60 min) make addresses unguessable and expire the attack window after your test. Expired addresses and their mail are purged.
- Sender pinning + authentication. Pin
expected_sender;sender_okis true only when the sender's domain aligns and the SPF/DKIM/DMARC verdicts (parsed from Cloudflare'sAuthentication-Resultsheader) pass. By defaultwait_for_emailignores anything that isn'tsender_ok. Unpinned addresses trust every sender — sowait_for_emailreturns a loudwarningfor them. - Untrusted-content framing. Email content — including
list_recentsubjects/snippets — is returned under anuntrustedkey with asecurity_notice, telling the driving agent to treat it as data, never instructions. - Ingest limits. Bodies are truncated and oversized messages are stubbed, so a huge/attacker email can't exceed the storage row limit or flood the agent's context window.
- Link flagging. Every link is classified —
private_ip/ip_literal(SSRF, incl. decimal/hex/IPv6 forms),sender_mismatch,non_https— so the agent (or you) can refuse a suspicious "confirmation" link before fetching.
What this does not do: it cannot stop a sufficiently clever injection in text the agent reads, and it cannot control what the agent fetches or where a link redirects (the fetch happens in the agent, not the Worker). So the flags are advisory — fetch flagged links with redirect: "manual" and human confirmation — and, most importantly, run the driving agent with least privilege: no production credentials or other secrets in the same session. Worst case should be "it clicked a bad link in a throwaway sandbox."
Operational hardening (recommended, configured on your account)
- Rotate tokens with zero downtime:
MCP_TOKENaccepts a comma-separated list, so setnew,old, roll clients, then dropold. - Rate-limit
/mcpwith a Cloudflare WAF rule (especially on401s) to throttle brute force. - Add a second gate with a Cloudflare Access service token in front of the Worker — free, and independent of the bearer check.
Notes & limits
- Single-user by design. One shared bearer token; no per-user accounts. It's a personal/team test tool, not a mail host.
wait_for_emailcaps at 120s per call (Cloudflare request limits). Poll again for longer waits.- Email Service is for transactional email; don't point bulk/marketing mail at it.
- Attachments aren't stored (bodies + links only). Add an R2 binding if you need them.
- Storage is a single Durable Object — plenty for a personal test inbox, not a multi-tenant service.
Development
npm run types # regenerate worker-configuration.d.ts from wrangler.jsonc
npm run typecheck # tsc --noEmit
npm run dev # local wrangler dev (note: inbound email only works when deployed)
License
MIT — see LICENSE.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
Neon Database
MCP server for interacting with Neon Management API and databases
E2B
Using MCP to run code via e2b.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.