Dead Simple Email
pilotctl appstore install io.pilot.deadsimpleAbout Dead Simple Email
Dead Simple Email gives an agent a real, deliverable email identity — not a sandbox — and it can get one entirely on its own. A single call to deadsimple.signup returns an account, an API key and a live inbox. No dashboard, no verification email, no human in the loop.
The method agents reach for most is deadsimple.get_verification_code. It pulls the one-time code or magic link straight out of the newest inbound message, so an agent can sign itself up for a third-party service or clear a 2FA prompt without ever parsing an email body. Filter by sender and by timestamp so a stale code is never returned. This is what turns "the provider emails you a code" from a dead end into a step.
From there the agent sends, replies on-thread, reply-alls, forwards, reads full messages with headers and attachments, and walks whole conversation threads over one REST API.
Underneath is real sending infrastructure: DKIM-signed egress through a dedicated MTA, bounce and complaint handling, suppression lists, and optional open and click tracking. Inbound mail can be pushed to HMAC-SHA256 signed webhooks that retry on exponential backoff. A supervisor agent can watch every inbox in a workspace at once with list_all_messages, and inboxes can be created in bulk, tagged, and torn down when a throwaway identity is finished.
Tiers. A self-provisioned agent account starts on trial: 1 inbox, 10 sends an hour, 25 a day. Calling deadsimple.claim with an email a human controls, then deadsimple.claim_verify, moves it to the Free plan — 5 inboxes and 5,000 emails a month — keeping the same API key, inboxes and history. Paid plans scale to 500 inboxes, 25 custom domains, and 1,000 requests a minute.
Edge cases. A 429 with code trial_send_limit_exceeded is a quota signal, not a transient failure — do not blindly retry, claim the account instead. Pagination is cursor-based. Spam is excluded from list_messages unless include_spam is true. Delete throwaway inboxes when finished so they stop counting against the quota.
Methods · 19
deadsimple.signupdeadsimple.claimdeadsimple.claim_verifydeadsimple.create_inboxdeadsimple.list_inboxesdeadsimple.get_inboxdeadsimple.delete_inboxdeadsimple.send_emaildeadsimple.get_verification_codedeadsimple.list_messagesdeadsimple.get_messagedeadsimple.replydeadsimple.reply_alldeadsimple.forwarddeadsimple.list_threadsdeadsimple.get_threaddeadsimple.get_attachmentdeadsimple.list_all_messagesdeadsimple.create_webhookFull usage demo
When your agent needs an email address of its own: to sign itself up for a third-party service and read the code back, or to send and receive real mail as part of a task.
pilotctl appstore call io.pilot.deadsimple deadsimple.signup '{"label":"my-agent"}'{"data":{"account_id":"...","api_key":"dse_...","inbox":{"inbox_id":"...","email":"[email protected]"},"plan":{"plan":"trial","inbox_limit":1,"sends_per_hour":10}}}Everything is under data. Save data.api_key as DEADSIMPLE_API_KEY — it is shown once. Already have a key? Skip this and call create_inbox.
Worked examples
pilotctl appstore call io.pilot.deadsimple deadsimple.get_verification_code '{"inbox_id":"<id>","since":"2026-08-06T12:00:00Z","from_contains":"stripe.com"}'{"data":{"found":true,"code":"481920","magic_link":null,"message_id":"..."}}Note the time BEFORE submitting your address, pass it as `since`, then poll every 2-3s. found=false just means it has not landed yet. Never parse the raw email yourself.
pilotctl appstore call io.pilot.deadsimple deadsimple.send_email '{"inbox_id":"<id>","to":"[email protected]","subject":"Report ready","text_body":"The run finished."}'{"data":{"message_id":"...","status":"queued"}}DKIM-signed egress, not a sandbox. Trial caps this at 10/hour, 25/day.
pilotctl appstore call io.pilot.deadsimple deadsimple.get_thread '{"inbox_id":"<id>","thread_id":"<tid>"}'{"data":{"thread_id":"...","messages":[{"message_id":"...","from":"...","subject":"..."}]}}Then reply with the message_id; threading headers are set for you.
Good to know
- Every response is wrapped: the payload is under data, with meta.request_id beside it.
- Call signup only when you have no key. It is idempotent per Idempotency-Key, so a retry returns the same account, not a second one.
- Trial is 1 inbox, 10 sends/hour, 25/day. A 429 trial_send_limit_exceeded is a QUOTA signal, not transient — do not retry it.
- Lift the caps with claim then claim_verify. Your key, inboxes and history survive.
- get_verification_code needs since from BEFORE you triggered the mail, or you may read a stale code.
Next
io.pilot.deadsimple deadsimple.help '{}'
What’s New
- First release on the Pilot app store.