address_for(site). wait_for_code(site). Done.
Your test suites and the agents inside your product stall at verification emails. Give them a deterministic inbox: the same site always maps to the same address, nothing exists until mail arrives, and the code comes back as text. No inbox IDs, no sending, no per-inbox pricing.
Built for flows you own or were asked to run: QA of your signup, your product's own onboarding, an agent acting for a person who approved it. Not a tool for bulk account creation: rate limits, a daily cap per key, per-site approvals for personal agents, and our acceptable-use policy enforce that, and mail is sealed to its owner's key at rest.
POST https://api.singleusemail.com/v1/agent/addresses
Authorization: Bearer ak_…
{ "site": "acme-app.com" }
→ { "site": "acme-app.com",
"address": "k7m3q2xa.h4@niirii.com",
"index": 0 }
GET https://api.singleusemail.com/v1/agent/codes?site=acme-app.com&wait=30
→ { "status": "ok", "code": "482913",
"from": "no-reply@acme-app.com",
"subject": "Your verification code",
"remaining": 19 } Three calls with curl
The fastest path is a developer key: open Your agents in the web app, connect an agent with This key is for a product I build switched on, and copy its key. Developer keys skip approvals and are metered on the developer tier (100 codes a month free). Then:
curl -X POST https://api.singleusemail.com/v1/agent/addresses \
-H "Authorization: Bearer ak_YOUR_AGENT_KEY" \
-H "Content-Type: application/json" \
-d '{"site":"acme-app.com"}' curl "https://api.singleusemail.com/v1/agent/codes?site=acme-app.com&wait=30" \ -H "Authorization: Bearer ak_YOUR_AGENT_KEY"
curl -X POST https://api.singleusemail.com/v1/agent/addresses \
-H "Authorization: Bearer ak_YOUR_AGENT_KEY" \
-H "Content-Type: application/json" \
-d '{"site":"acme-app.com","new_domain":true}' | 200 ok | code and/or link, sender, subject, remaining allowance |
| 200 waiting | nothing yet after wait seconds (max 60); call again |
| 202 awaiting_approval | the key's owner must approve this site first |
| 402 limit | allowance used; the body carries upgrade, a link to buy a pack or raise the tier |
| 403 denied / agent_paused | the owner denied or paused this agent |
Four tools any agent can call
Streamable HTTP, JSON-RPC 2.0, protocol 2025-06-18. No OAuth dance: the agent key is the URL, or a Bearer header on POST https://api.singleusemail.com/mcp.
| Tool | Arguments | Returns |
|---|---|---|
| address_for | site, optional new_domain | the address for this site |
| wait_for_code | site, optional wait (≤60 s) | code and/or link, or a status to act on |
| get_link | site | the newest verification link for this site |
| list_addresses | none | the addresses this agent has made |
{
"mcpServers": {
"singleusemail": {
"type": "http",
"url": "https://api.singleusemail.com/mcp/ak_YOUR_AGENT_KEY"
}
}
} Two principals, one base URL
Base URL https://api.singleusemail.com. JSON in, JSON out. Errors are { "error": "snake_case", "message": "…" } with a fitting status. CORS is open, so browsers can call it directly.
Agent endpoints — Authorization: Bearer ak_<agent key>
| Method and path | Body or query | What it does |
|---|---|---|
| POST /v1/agent/addresses | site, new_domain? | Derive and register the agent's address for a site (idempotent). |
| GET /v1/agent/codes | site, wait?, since? | Long-poll for the newest unread code or link. Metered. |
| GET /v1/agent/addresses | — | List this agent's sites and addresses. |
Owner endpoints — Authorization: Bearer <auth token>
The auth token is base64url(HMAC-SHA256(key, "sum:auth:v1")). The server keeps SHA-256(token) as the fingerprint that owns addresses and agents; it never sees the key.
| Method and path | Body or query | What it does |
|---|---|---|
| GET /v1/domains | public | The pool domains that currently issue addresses. Derive against this list. |
| POST /v1/addresses | address, site_hash, index?, label_enc? | Register a derived address so mail to it is accepted. 201 new, 200 existing, 409 if another key holds it, 429 over the hourly limit. First registration may require a Turnstile token in X-Turnstile. |
| GET /v1/addresses | — | Your addresses with counts, last activity and encrypted labels. |
| PATCH /v1/addresses/:address | label_enc | Store or clear an encrypted label (ciphertext only; you hold the key). |
| DELETE /v1/addresses/:address | — | Retire: stops receiving, deletes its mail now. |
| GET /v1/codes | address, since?, wait? | Long-poll for the newest message on one address (code, link, sender, subject). |
| GET /v1/messages | address? | Recent messages (metadata, code, link, DKIM/SPF result). |
| GET /v1/messages/:id | — | One message with text and sanitised HTML. |
| GET /v1/messages/:id/raw | — | The original .eml. |
| DELETE /v1/messages/:id | — | Delete a message and its raw copy. |
| GET /v1/plan | — | Plan and retention for this key. |
| POST /v1/agents | agent_id, agent_hash, name?, codes_only?, ask_new_sites?, monthly_limit? | Register an agent key by its hash with its rules. Upsert. |
| GET /v1/agents | — | Agents, pending approvals, and the record (last 100 events). |
| PATCH /v1/agents/:id | paused?, codes_only?, ask_new_sites?, monthly_limit?, name? | Change rules or pause. |
| DELETE /v1/agents/:id | — | Void the agent key. Its addresses stay with you. |
| POST /v1/approvals/:id | decision: allowed_once | always | denied | Answer an agent's request for a new site. Deny pauses the agent. |
| POST /v1/abuse | target, category, details?, email? | Public. Report an address or message. |
Derivation, so you can do it in any language
key = 32 random bytes
formatted "sum1-" + base32(key) in groups of 4
authToken = HMAC-SHA256(key, "sum:auth:v1")
agentKey = HMAC-SHA256(key, "sum:agent:v1:" + agent_id)
agent_hash = hex(SHA-256(agentKey)) # what you register
site = lowercase host, no scheme/www/path/port
tag = base32(HMAC(secret, "sum:tag:v1"))[0:2]
local = base32(HMAC(secret, "sum:addr:v1:" + site + ":" + index))[0:8] + "." + tag
domain = pool[ uint32be(HMAC(secret, "sum:dom:v1:" + site + ":" + index)[0:4]) % len(pool) ]
address = local + "@" + domain
site_hash = base32(HMAC(secret, "sum:site:v1:" + site))[0:16]
# secret = key for your own addresses, agentKey for an agent's
# base32 = RFC 4648 alphabet, lowercase, no padding Per code. Never per inbox.
Every tier has unlimited address creation and unlimited developer keys. You pay for verification emails you actually read through the API: the monthly allowance first, then prepaid packs, which never expire. Buy packs or upgrade from the developer card in the app; all developer keys of one key holder share the tier.
- 100 codes / month
- 1-hour retention
- Shared pool domains
- MCP and REST
- No monthly fee
- 7-day retention
- Shared pool domains
- Card on file
- 5,000 codes included, then $7 / 1,000
- 30-day retention
- 1 private domain
- Tenant keys
- 30,000 codes included, then $5 / 1,000
- 90-day retention
- 5 private domains, SLA
- White-label and DPA on request
Sold through Polar (polar.sh), our merchant of record. Prices in USD; local taxes may apply. A tier that lapses falls back to pay as you go; prepaid codes stay. Private domains and tenant keys on Pro and Scale are set up by hand for now: write to us.
What we promise, and what we do not yet
Before you integrate
Where do keys come from?
From you. A key is 32 random bytes; nothing is issued by the server. Make one in the web app (Your key) or in code, derive the auth token and agent keys locally, and register addresses as you derive them. The server stores only one-way hashes.
Why register addresses at all if they are derived?
So mail to an address nobody derived is refused at the door and never stored. Registration is idempotent and free: one POST per address, or let the agent tools do it for you.
Can I use my own domain?
On Pro and Scale: point one MX record at us and your tenant's addresses come out on your domain. On the shared pool, a site that blocks one domain is handled by index bumping (new_domain).
Is there sending?
No, and there never will be. Receive-only keeps the pool clean, the price low and the abuse surface small. Export raw .eml if you need the message elsewhere.
How fast is delivery?
The email worker parses the message and writes it before the SMTP session closes; wait_for_code long-polls the store. In our tests the code is readable within a few seconds of the sending server handing it off. We do not publish a latency guarantee yet.
What about rate limits?
Address registration is limited per key per hour (30 on free). Agent codes are metered per month. Abuse reports can disable an address or a key. Contact us before running load tests.
Looking for a single-use address for yourself?
Same engine, friendlier door: singleusemail.com gives you an address in one click, no key handling needed.