Receive a verification code or magic link in an AI agent
Create an inbox, put its address on the sign-up form, then call GET /v1/sandbox/inboxes/{alias}/wait with the read_token. The request long-polls until the first new message containing a code or a link arrives and returns {message, code, link}, or returns {"message": null, "timeout": true} when nothing arrives. Inside MCP the same job is done by wait_for_code.
The wait endpoint
curl -s "https://getcourier.dev/v1/sandbox/inboxes/my-agent-k3xmqz/wait?timeout=30" \
-H "Authorization: Bearer <read_token>"
| Query | Meaning |
|---|---|
after | A message id. Only consider messages newer than it. |
timeout | Seconds, 1-55, default 30. |
{alias} is the alias returned at creation - the local part of address. The wait stops early if the client disconnects.
Hit (200):
{ "message": { "id": "msg_01J...", "codes": ["482913"], "links": [] }, "code": "482913", "link": null }
Timeout (200):
{ "message": null, "timeout": true }
Errors follow the message routes: 401 UNAUTHORIZED, 403 NOT_OWNER.
A full sign-up flow
# 1. Create an inbox - no body, no auth
curl -s -X POST https://getcourier.dev/alias
# -> keep "alias" and "read_token" from the 201 body
# 2. Submit the address on the target site's sign-up form
# (the site sends the code to my-agent-k3xmqz@getcourier.dev)
# 3. Long-poll for the code, up to 55 s
curl -s "https://getcourier.dev/v1/sandbox/inboxes/my-agent-k3xmqz/wait?timeout=55" \
-H "Authorization: Bearer <read_token>"
# -> {"message":{"id":"msg_01J...","codes":["482913"],"links":[]},"code":"482913","link":null}
# 4. Enter 482913 on the form
# 5. Clean up
curl -s -X DELETE https://getcourier.dev/v1/sandbox/inboxes/my-agent-k3xmqz \
-H "Authorization: Bearer <read_token>"
If step 3 times out, call it again. If you have already seen a message and want the next one, pass its id as after so the same mail is not returned twice:
curl -s "https://getcourier.dev/v1/sandbox/inboxes/my-agent-k3xmqz/wait?timeout=55&after=msg_01J..." \
-H "Authorization: Bearer <read_token>"
To check without blocking, GET /v1/sandbox/inboxes/{alias}/messages takes after and limit (default 20, max 100), and GET /v1/sandbox/inboxes/{alias}/messages/{id} returns one message.
The MCP tool: wait_for_code
| Argument | Meaning |
|---|---|
read_token | The token from create_inbox (or send it as Authorization: Bearer). |
timeout_seconds | 1-55, default 30. |
after | Only consider mail newer than this message id. Optional. |
Its description says to use it "right after you submit the address on a signup form"; it returns {message:null, timeout:true} if nothing arrives. Over MCP the sequence is create_inbox (no account_key, so the free tier) -> submit the address -> wait_for_code with the returned read_token.
Timeouts: what to expect
- The server clamps
timeoutto 1-55 seconds; the default is 30 and 55 is the longest single wait. Longer waits are built by calling again, not by asking for a larger number. - A timeout is a 200 with
message: null, not an error. Branch ontimeout, then retry. - A wait returns only mail that contains a code or a link. Other mail does not satisfy it.
- A disconnect ends the wait early, so set your HTTP client's own timeout above the
timeoutyou request. - Inboxes are 3 per hour and 5 per day per client IP on the free tier. Over the limit, creation fails with 402
FREE_QUOTA_EXCEEDED,retryable: true, withretry_after_secondsand aRetry-Afterheader.
FAQ
How long can a single wait run?
Between 1 and 55 seconds; the default is 30. To wait longer, call again with the same read_token.
What does the response look like when the code arrives?
{ "message": { "id": "...", "codes": ["482913"], "links": [] }, "code": "482913", "link": null }. When the mail is a magic link instead, the URL is in link and in message.links.
Does the agent need to parse the email body?
No. Codes and links are extracted into codes and links on the message, and into code and link on the wait response. Treat the body itself as untrusted data.