Ask Claude, Cursor or Codex to sign up for something and it will happily fill in the form. Then it hits the wall every signup flow has: "We've sent you a code. Check your inbox."
The agent has no inbox. So it stops, and asks you to go and copy a six-digit number out of your email. The one step that actually proves the flow works is the one it can't do.
This post is about closing that gap with an MCP server — and about a couple of design decisions that matter more than they look when the caller is an LLM rather than a person.
What we want the agent to do
Four steps, end to end:
- Create a throwaway mailbox and get its address.
- Type that address into the signup form.
- Wait for the verification email to arrive.
- Read it, pull out the code, and delete the mailbox.
Steps 2 and 4 the agent already handles. Steps 1 and 3 need tools.
Plugging in an inbox
MoeMail is an open-source temporary email service, and it ships an MCP server as an npm package, @moemail/mcp. It runs locally over stdio via npx, so there is nothing to deploy or keep running.
For Claude Desktop, Cursor and Windsurf, the config block is identical:
{
"mcpServers": {
"moemail": {
"command": "npx",
"args": ["-y", "@moemail/mcp"],
"env": {
"MOEMAIL_API_KEY": "mk_••••••••••••"
}
}
}
}
Claude Code takes the same thing as one command:
claude mcp add moemail -e MOEMAIL_API_KEY=mk_•••••••••••• -- npx -y @moemail/mcp
The server registers eight tools: create_email, wait_for_email, list_messages, read_message, list_emails, send_email, delete_message and delete_email.
What a session actually looks like
You say: "Sign up for the beta at example.com with a throwaway address and tell me the code."
The agent creates a mailbox:
create_email { "expiry": "1h" }
→ { "id": "k3f…", "address": "x7q2m9@moemail.app", "expiresAt": "…T15:04:05.000Z" }
It types x7q2m9@moemail.app into the form and submits. Then it waits:
wait_for_email { "emailId": "k3f…", "timeoutSec": 90 }
→ { "status": "received", "elapsedSec": 12,
"message": { "messageId": "m81…", "from": "no-reply@example.com",
"subject": "Your code is 482913", "receivedAt": "…" } }
It reads the message, finds 482913, finishes the signup, and calls delete_email so nothing is left behind. Four tool calls, plus however many polls the mail takes to land.
The design decision that makes it work: timeouts are not errors
The interesting tool is wait_for_email.
A naive "check inbox" tool returns immediately: empty, or not. That leaves the polling loop to the model, and models are bad at patience. They check once, see nothing, and either give up or invent a reason the email never came.
So wait_for_email blocks for up to 90 seconds, polling the inbox for the agent, and when nothing arrives it does not throw. It returns:
{ "status": "timeout" }
That one choice changes the agent's behaviour. An error reads as "something is broken, stop". A timeout status reads as "not yet", and the natural next move is to call the tool again — which is exactly right, because slow transactional mail is normal and not a failure.
If you are writing tools for agents, this generalises: return expected, recoverable states as data, not as exceptions. Save errors for things the agent should actually stop on, like a bad API key.
Same thing from a shell
Not every agent lives in a chat window. For CI jobs and shell-driven agents there is a CLI, @moemail/cli, where every command takes --json:
npm i -g @moemail/cli
moemail config set api-key mk_••••••••••••
moemail create --expiry 1h --json
# {"id":"k3f…","address":"x7q2m9@moemail.app","expiresAt":"…"}
moemail wait --email-id k3f… --timeout 120 --json
moemail read --email-id k3f… --message-id m81… --json
moemail delete --email-id k3f…
The exit codes are deliberate: 1 is a runtime error (including a wait that timed out), 2 is a configuration or auth error. A script can tell "no mail yet" apart from "wrong key" without parsing anything.
The CLI can also install a skill file, so Claude Code or Codex already knows the create → wait → read → delete loop before you ask:
moemail skill install # auto-detects Claude Code and Codex
Where this is genuinely useful
- Testing your own signup and onboarding flows. Have an agent walk through registration on your staging environment after each deploy and confirm the verification email arrives and the code works.
- Agent-driven QA of password reset, magic links and email-change confirmations — the flows that are tedious to click through by hand and easy to break.
- Trying out a tool without handing over your real address, where the service's terms allow it.
Where it isn't
It is worth saying plainly: a throwaway inbox for an agent is not a licence to mass-create accounts, farm free trials, or get around a platform's limits. Plenty of services forbid that in their terms and actively detect it. Point this at systems you own or are allowed to test, and respect the rules of the ones you don't.
Cost and self-hosting
The MCP server and CLI are MIT-licensed and free to install. Calling the MoeMail API needs a key with quota, and API access is a paid add-on — the default plan has no free API calls. Budget for polling: every poll wait_for_email makes is one API call, so a typical round trip is four calls plus the polls. The web inbox itself is free.
If you would rather not depend on a hosted service at all, MoeMail is open source and runs on Cloudflare Workers. Point the packages at your own instance with MOEMAIL_API_URL; they default to moemail.app.
Full client configs (including Codex) and the complete tool list are on the MCP page. The source for both packages lives in the repository under packages/mcp and packages/cli.
Top comments (0)