Blog

An email sandbox for AI agents

Test mode in Pidgeon is the same API with a different key: the whole pipeline runs, nothing leaves, nothing counts, and you can cause a bounce, a refusal or a lost answer on purpose.

The code that handles failure never runs

An agent that sends email has a happy path and a dozen others: a recipient the provider refuses, a bounce that arrives a minute later, a rate limit, an answer that never comes back. Those are the branches most likely to be wrong, and a working email API never exercises them. Testing against real mail is worse: it sends to real people and spends a real plan.

Pidgeon has a test mode for exactly this. It is the same API with a different key, not a separate sandbox to keep in step.

A test key runs the whole pipeline

Mint a key with "test": true, or pick Test in the dashboard. Its token starts pgn_test_, and everything works as it does in production: validation, threading, events and signed webhooks, delivery receipts, Idempotency-Key and the Pidgeon Firewall. What changes is where the mail goes:

  • Nothing leaves Pidgeon. Mail to your own account's addresses is delivered there, marked test. Anything else is recorded as sent and goes nowhere.
  • Nothing counts against your plan. Rate limits still apply, in a bucket of their own.
  • It cannot touch production. A test key mints only test keys, and cannot change domains, live addresses or app passwords.

The SDKs, the CLI and the MCP server take a test key like any other, so your agent runs unchanged.

Test addresses that clean up after themselves

A test key can create addresses of its own. They are marked test, count against no plan (up to 100 at once), take only test mail, and are deleted a day after they are created, so a CI run can make fresh addresses every time without leaving any behind.

Simulated inbound mail

To test what your agent does when mail arrives, send it some:

curl https://pidgeon.ai/api/v1/test/inbound \
  -H "Authorization: Bearer $PIDGEON_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "'"$TEST_ADDRESS"'",
    "from": "customer@example.com",
    "subject": "Where is my order?",
    "text": "It was due on Monday."
  }'

It goes through the real inbound path: sender rules, the spam filter, the Firewall, threading, message.received and your webhooks. Add in_reply_to and it joins an existing thread.

Failures on demand

With a test key you can cause each failure on purpose:

Do thisAnd get
Send to rejected@ any domainThe recipient comes back in the send's rejected, with a reason; the others still get it
Send to bounce@ any domainAccepted, then a hard bounce and message.bounced a few seconds later
Header Pidgeon-Test-Fail: 429 or 503That error with Retry-After, and nothing done
Header Pidgeon-Test-Fail: delay=20sThe work done at once, the answer 20 seconds later
Header Pidgeon-Test-Fail: dropThe work done, and 504 test_dropped instead of the answer

delay and drop do the work before they misbehave, so a retry with the same Idempotency-Key gets the original answer back. That is the case your retry logic has to get right, and now you can watch it do so. A +tag on the magic addresses (bounce+run-41@example.com) keeps parallel runs apart.

Start

Test mode is on every plan, including Free. The test mode page has the short version and the guide every trigger and limit; sign up, mint a test key, and point your test suite at it.

Give your agent an address.

Paste one prompt, or make the call yourself. It is free to start.

Any agentprompt

Read https://pidgeon.ai/skill.md and follow the instructions to get your own inbox.

The full API reference is at https://pidgeon.ai/openapi.json

SignupTwo calls, and one code emailed to you