Skip to the documentation
API

Disposable inboxes

A working address for somebody who has not got one: no account, no key, and gone the same day.

What a disposable inbox is

A caller asks for an address on a domain this install owns, watches it for a few minutes, reads whatever arrives, and abandons it. It exists for the confirmation code, the "what does this form actually send" question, and the signup you do not want attached to the address you will still be using in five years.

  • It receives and nothing else. There is no send: an inbox has no identity to send as, and none of these nine calls will put a message on the wire.
  • The lease is 60 minutes by default and can be pushed to 24 hours, an hour at a time.
  • It holds 50 messages, counted as they arrive. Mail reaching a full inbox is dropped rather than queued, and deleting one does not buy room for another.
  • At the end of the lease the mail is DELETED, not hidden and not archived. The row outlives it by a week so the address cannot be re-issued while a slow sender is still retrying to it.
  • None of it touches a mailbox. A disposable message lives in its own table, and no query on this path can reach a real one.

These are the same calls the free tool on this site makes, so anything the page can do your code can do. The API is here for the case the page is not: a test suite that wants a fresh address per run.

The address is not the credential

A disposable address is typed into a signup form the moment it is issued. From there it travels in a To: header, through the sender’s logs, and into whatever CRM sits at the other end. If knowing the address were enough to read the mail, the tool would leak every inbox it issued, by design, and to precisely the party the caller was holding at arm’s length.

So creating an inbox returns a second value: a token, 32 random bytes as oe_inbox_ plus 43 base64url characters. It appears on that one response and on no other. The row keeps only a keyed hash of it, so nothing recovers it, not a support request and not a database dump. Lose the token and you have lost the inbox, which is the correct outcome for a credential that reads somebody’s mail.

The whole flow
# 1. Mint one. This is the only response that carries a token.curl -s -X POST "$OE/temp-mail/inboxes" -H "Content-Type: application/json" -d '{}' # 2. Keep it, and read with it.export INBOX="Authorization: Bearer oe_inbox_kQ8v…"curl -s "$OE/temp-mail/inboxes/tinb_9c2f…/messages" -H "$INBOX"

Send an oe_live_ or oe_test_ API key to one of these routes and it is refused as invalid_credential_type rather than as a flat 401. Two kinds of credential share one host and one header here, and "unauthorised" would leave you to guess which of yours was wrong.

The lease, and extending it

An hour, rather than the ten minutes the genre is named after. Ten is enough for a confirmation code and not enough for the other half of what these are used for: a trial that emails you again the next morning, a form filled in twice because the first attempt timed out. ttlMinutes on create asks for something else, from 1 to 1440; a number outside that is refused with a 422 rather than quietly adjusted, because an expiry you did not ask for is one you have already planned around.

POST /temp-mail/inboxes/{id}/extend adds an hour to the expiry, not to now, so extending early does not waste the time you have left. It works 23 times, and the day from the moment the inbox was created is the harder of the two caps: a lease that already runs to it has nothing left to buy, however few extensions have been spent. extensionsLeft on every inbox response counts both, so a client can grey the button out; at zero the call answers 409 extension_limit.

An expired inbox stops authenticating the instant it expires: its token answers 404 without waiting for the sweep. The sweep is what deletes the mail, and it runs on the hourly cron; DELETE /temp-mail/inboxes/{id} is the same deletion on demand.

The ceilings

All of these are counted rows rather than a rate limiter. There is no limiter in this codebase to reach for, and saying so is more use than implying a defence that is not there. They are placed where the damage would be: minting, and storage.

CeilingValueWhat happens at it
Lease60 minutes, extendable to 24 hours409 conflict_error / extension_limit
Messages per inbox50Further mail is dropped at the door. No bounce is written, nothing is queued, and deleting a message does not give the slot back.
Inboxes minted6 per hour, 30 per day, per caller429 rate_limit_error / too_many_inboxes
Stored body2 MBtruncated: true on the message; the rest of it is gone.
Attachment bytes8 MB eachcontent is null and the metadata is kept, which is not the same as an empty file.

The minting ceiling counts against a keyed hash of the client IP, and a destroyed inbox still counts, so throwing one away is not a way to buy another. Behind somebody else’s proxy the forwarded header can be spoofed, which is a known weakness of the ceiling rather than a hole in the credential: nothing here authorises on that value.

What is not here

Not shipped yet

Finding out by trying is worse than being told:

  • No sending, in any form. A disposable inbox has no connection to send as, and adding one would make an anonymous, unauthenticated endpoint into an open relay.
  • No rename. Changing your address means creating a second inbox: renaming in place would release the old local-part the instant it was clicked, and a confirmation already in flight would then be delivered to whoever was issued it next.
  • No rules, filters, forwarding, webhooks or AI. spam is a flag on the message and nothing acted on it. Nothing was filed away, and nothing here is summarised or embedded.
  • No bounces. Mail to a pooled domain that names neither a live disposable inbox nor an address the operator created is dropped silently, on purpose: a public address generator attracts dictionary attacks, and writing a delivery report to whatever return path the attack claims would make the install a backscatter source.
  • No pooled domain on the hosted service yet. Until an operator enrols one, GET /temp-mail/domains answers with an empty list and creating an inbox answers 503 temp_mail_unavailable. Inbound delivery to a pooled domain has not been observed end to end on a live domain.

Enrolling a domain, if you run the install

The pool is data, not configuration: whatever is enrolled is what gets handed out. Nothing automates the DNS, so four of these five steps are a human at a registrar.

  1. Register a domain for it. Use one you are willing to let strangers hand out. Every address on it shares its reputation, which is also why the picker spreads new inboxes across the pool at random rather than filling the first one.
  2. Add it in the app under Settings → Domains. That mints the sending identity and prints the DNS records to publish.
  3. Publish the MX, SPF, DKIM and _openemail-challenge TXT records at the registrar. Verification reads live DNS and is re-checked on the cron; only a verified domain is ever offered.
  4. Enrol the verified domain in the disposable pool with the operator script. Until it is enrolled it is an ordinary domain on the workspace.
  5. Turn catch-all OFF. It is on by default now, and the enrolment script refuses a domain until it is off. Disposable delivery is decided before the ordinary recipient lookup, so no address row is ever written for a pooled domain; leaving catch-all on would start filing throwaway mail into a real mailbox.

Reserved local-parts (postmaster, abuse, security and the rest of RFC 2142) can never be disposable and fall through to the ordinary mailbox instead. A pooled domain that black-holes its own abuse reports is a domain that stops being able to deliver anywhere. An address you create on a pooled domain yourself, such as legal@ or privacy@, behaves the same way: mail to it lands in your mailbox, and nobody can be issued it as a disposable address.

In this section