Quickstart

From nothing to a message read back over HTTP, in four commands. No account, no key, no SDK.

1. Pick an address

Any name, on any of our public domains. There is nothing to call and nothing to register — the address becomes real the moment something is delivered to it.

Keep it unguessable if you would rather nobody wandered in: on a public domain the address is the only thing standing between the mailbox and the world.

shell
$ ADDR="ci-$(openssl rand -hex 4)@grabmail.io"; echo "$ADDR"

2. Send it something

Use it in the signup form, the password-reset flow or whatever you are testing. Delivery is normally a couple of seconds.

Our mail servers accept messages up to 5 MB. Anything larger is refused at SMTP time, so the sender is told immediately rather than left wondering.

shell
$ curl -sX POST https://your-app.example/signup --data-urlencode "email=$ADDR"

3. Poll the mailbox

One request per second is the intended rhythm and is never throttled. An empty mailbox answers 200 with count: 0 — never a 404, so your loop does not need to special-case it.

This snippet gives up after a minute rather than spinning forever, which is what you want in CI.

poll until it arrives
$ for i in $(seq 60); do
  ID=$(curl -sG https://grabmail.io/api/v1/mailbox --data-urlencode "address=$ADDR" | jq -r '.messages[0].id // empty')
  [ -n "$ID" ] && break
  sleep 1
done; echo "$ID"

4. Read it, then throw it away

Fetch the message and pull out whatever you came for — here, a six-digit code from the plain-text part.

Deleting is optional: everything goes on its own after 5 days. Do it anyway in CI, and the next run starts from a clean mailbox.

read and delete
$ curl -sG https://grabmail.io/api/v1/message/$ID --data-urlencode "mailbox=$ADDR" \
  | jq -r .text | grep -oE '[0-9]{6}'

curl -sX DELETE -G https://grabmail.io/api/v1/message/$ID --data-urlencode "mailbox=$ADDR"

If the address gets rejected

It happens, and it is not a bug. The public domains are on the public disposable-mail blocklists, and a large share of signup forms check them. Changing the part before the @ does not help — it is the domain being refused.

Point your own domain at us and it goes away, for free: connect a domain. If you would rather not use one of your own, a paid plan opens 92 private .com domains, kept off those lists — same four calls, plus an Authorization: Bearer header. See the plans.

Wiring it into CI

Two habits keep a suite that reads real mail from becoming flaky:

A fresh address per run
Derive it from the build id or a random suffix. Reusing one address across runs means yesterday’s message can satisfy today’s assertion.
A deadline, not a retry count
Poll against a wall-clock timeout as above. Retry counts silently become longer as your sender gets slower.
Never assert on delivery time
Mail is not synchronous. Assert that the message arrives, not that it arrives within a specific second.
Use your own domain for anything real
A public mailbox is readable by anyone who guesses the address — and the public domains are printed on this site, so they are guessed. On a domain of your own, an address is only findable by someone who already knows it. One MX record, and nothing else to do.

For AI agents: MCP

An agent cannot read this page, decide which endpoint it wants and write a request. It asks a server what tools exist and calls them. grabmail runs a Model Context Protocol server for exactly that, at https://grabmail.io/mcp — no key, no account.

Point any MCP client at the endpoint. Claude Desktop, Cursor, Continue and the OpenAI Agents SDK all read this shape.

Six tools: create_inbox, list_domains, list_messages, read_message, delete_message and wait_for_message.

mcp.json
$ {"mcpServers":{"grabmail":{"url":"https://grabmail.io/mcp"}}}

wait_for_message is the one that matters. The reason an agent wants a disposable inbox is almost always a confirmation code standing between it and the next step. Rather than polling in a loop it burns tokens on, it asks for an address, submits the form, and blocks until the mail lands:

Waits up to 25 seconds and returns the message in full — sender, subject, body — or says it timed out so the agent can simply call again.

It can also wait for a particular message: pass subject_contains or from_contains and anything else that arrives is ignored.

MCP · wait_for_message
$ curl -sX POST https://grabmail.io/mcp -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"wait_for_message","arguments":{"address":"you@grabmail.io","subject_contains":"code"}}}'

There is also an OpenAPI 3.1 spec (YAML) for code generators, and llms.txt for anything that would rather read plain text.

Next

API reference

Every parameter, every status code, the error shape.

Open the reference →

Limits

What is capped, what is not, and what happens at the ceiling.

Open the limits →

Your own domain

One MX record, and every address on your domain works here — nothing to register.

See the setup →

Welcome back

Your inboxes and your domains, in one place.