TempMail.cc - Free Temporary Email Service Logo TempMail.cc
Home About FAQ

Temp mail for developers

This page documents how to receive mail from a script. There is no REST API and no API key: the web inbox and your script speak the same Socket.IO protocol. Mail is pushed the instant it arrives, and the whole service is one open-source Node process that you can also run yourself. If you came here looking for a GET /messages endpoint, read the section on why one does not exist before designing around it.

A working example

Install the client with npm install socket.io-client, save the following as inbox.mjs and run node inbox.mjs. It claims a random address, prints it, and logs the first message that lands there. This is the same sequence the browser uses when you open the home page.

import { io } from 'socket.io-client';

const ORIGIN = 'https://tempmail.cc';
const socket = io(ORIGIN, { transports: ['websocket'] });

socket.on('connect', () => socket.emit('request shortid'));

socket.on('shortid', (name) => {
  console.log(`Send mail to: ${name}@tempmail.cc`);
});

socket.on('mail', (mail) => {
  console.log('Subject:', mail.headers.subject);
  console.log('From:   ', mail.headers.from);
  console.log('Body:   ', mail.text || mail.html);  // a verification code lives here
  socket.close();                                  // one mail is enough for a test
});

socket.on('shortid rejected', (e) => console.error(e.code, e.message));
socket.on('connect_error', (e) => console.error('Connection failed:', e.message));

Copy the printed address into whatever form you are testing and trigger the mail. In a test runner, wrap the mail event in a Promise with a timeout instead of logging it — scripts/e2e.mjs in the repository does exactly that, including a hand-rolled SMTP client that sends the test message.

To claim a name you choose instead of a random one, replace the connect handler:

socket.on('connect', () => socket.emit('set shortid', 'signup-test-01'));

That name is not reserved for you. It is first come, first served, has no password, and is released when the last listener disconnects. Add a run-specific suffix for parallel jobs rather than reusing one literal across a test suite.

Why there is no REST endpoint

Mail is not stored in a database. It is parsed in a worker thread, placed in an in-memory replay buffer, and pushed to whoever is listening; the buffer is swept 30 minutes after the last message. There is no durable message list to page through, and adding one would require the database, accounts and retention policy this service deliberately does without.

The consequence for your code: subscribe, do not poll. A claim is followed by one mail history event containing whatever is still buffered (at most 20 messages), then live mail events. Reconnects replay the buffer, so deduplicate by _id. The boot event carries a per-process id; if it changes, the server restarted and every mailbox, id and recovery token from before is gone — clear local state instead of merging the two generations.

Client → server events

EventArgumentsEffect
request shortidclientToken?Allocate a random 8-character name and claim it.
set shortidname, clientToken?Claim a specific name. Rejected if invalid, reserved, or held by a different token.
recovertoken, clientToken?Reclaim the mailbox behind a recovery token, kicking current listeners.
delete mailmailId, ack?Remove one message from your own mailbox's buffer; ack receives { ok }.

clientToken is an arbitrary per-tab string. It lets the server tell your own reconnect from a stranger claiming the same name: the same token takes over a stale socket, a different one gets E_SHORTID_TAKEN. A one-shot script can omit it. It is not authentication and grants no privacy.

Server → client events

EventPayload
shortidname — the mailbox now bound to this socket.
shortid rejected{ name, code, message }.
boot{ id } — process id; a change means the in-memory store is gone.
mailOne parsed message (shape below).
mail historyMail[] — replay buffer, emitted once right after a claim.
mail deleted{ id } — broadcast to every socket on that mailbox.
recovery{ token, expiresAt } — a bearer credential for reopening the inbox.
recovery rejected{ message } — the token expired or is invalid.
server error{ code, message } — a notification; the socket stays open.

A mail payload contains only the parts the parser kept. Headers are flat strings, and html may be null:

{
  "_id": 42,
  "headers": {
    "from": "Acme <[email protected]>",
    "to": "[email protected]",
    "subject": "Your verification code",
    "date": "2026-11-05T07:00:00.000Z"
  },
  "recipients": ["[email protected]"],
  "html": "<p>Your code is 482913</p>",
  "text": "Your code is 482913"
}

Routing uses the parsed recipient addresses, never the raw To: header text, so a display name containing @ cannot deliver a copy to an unrelated mailbox. recipients holds the structured addresses from the envelope. Attachments are stripped before any of this reaches a socket.

Error codes

CodeWhereMeaning
E_SHORTID_INVALIDclaimName failed the allowlist: 1–32 chars of a-z0-9._-, no leading or trailing dot or dash.
E_SHORTID_RESERVEDclaimName is reserved (admin, postmaster, abuse, …).
E_SHORTID_TAKENclaimAnother live socket holds it and your token is not an owner token.
E_RATE_LIMITEDnotificationPer-mailbox budget for machine-generated notices exhausted this minute. Real mail is never throttled.
E_MAIL_TOO_BIGnotificationA message exceeded the size cap and was cut mid-DATA; the sender gets an SMTP error.
E_NOT_FOUND / E_INTERNALHTTPReturned as X-Error-Code; JSON clients get { ok, code, message }.

User-facing wording lives server-side, so a new code ships without a frontend deploy. Unknown codes degrade to E_INTERNAL client-side.

Limits that change how you write the test

  • No authentication. The mailbox name is the only credential for live mail. Anyone who knows or guesses it can subscribe. Send synthetic data only, never real user mail.
  • 30 minutes, not forever. A mailbox expires 30 minutes after its most recent message; each new arrival resets the clock. The buffer keeps at most 20 messages per mailbox and 64 MB globally, evicting the soonest-to-expire mailbox first.
  • No attachments, no large mail. Only text and HTML bodies are forwarded. Messages over 5 MB are refused during the SMTP conversation, so the sender is told instead of believing it was delivered.
  • Inbound SMTP is plaintext. AUTH and STARTTLS are disabled; the hop from the sending MTA to this host is not encrypted.
  • One process only. State is in memory. Restarting clears every mailbox and recovery token; running two replicas splits mail and loses replay history. Do not put a CDN or proxy in front of the mail port.
  • Every recipient is accepted at RCPT time. Routing happens after DATA, by recipient local part. A name that fails the allowlist lands nowhere.
  • Recovery is a takeover, not a mirror. recover disconnects current listeners. Two processes sharing one token will kick each other.
  • Disposable domains get blocked. Many sign-up forms check public blocklists. When that happens the mail is never transmitted; no setting here changes it.

There is also no outbound path: the service receives only. If your flow depends on a reply from the same address, it will not complete here.

Self-hosting

The server, SMTP gateway and frontend are GPL-2.0 and in one repository. Build and run with port 25 reachable directly and an MX record pointing at the host:

docker build -t tempmail .
docker run -d --name tempmail \
  -p 3000:3000 -p 25:25 \
  --restart unless-stopped \
  tempmail

curl localhost:3000/healthz     # {"ok":true}

Web traffic may sit behind nginx or a CDN; mail may not. Run exactly one instance. The knobs most likely to matter in a test environment are MAIL_TTL_MINUTES (30), MAIL_STORE_MAX_PER_MAILBOX (20), MAIL_STORE_MAX_BYTES (64 MB), SMTP_MAX_MAIL_BYTES (5 MB) and REALTIME_MAX_BROADCASTS_SEC (200). The repository README lists all of them, docs/protocol.md is the authoritative event table, and docs/operations.md explains memory sizing and the failure modes of scaling out.

Good fits and bad fits

Good fits: exercising your own sign-up or password-reset flow, end-to-end tests that need a verification code, QA environments, demos, and anything where a synthetic address is thrown away with the test run. A self-hosted instance keeps the domain out of public disposable-domain lists, which removes the most common cause of a missing code.

Bad fits: real user correspondence, anything containing personal data, attachments, messages you need tomorrow, or several machines all claiming the same fixed name. Nothing here is private and nothing here is durable. For an account you intend to keep, use a real mailbox.

Questions

Is there a REST API?

No. There is no REST endpoint, no message-list URL and no webhook. Claim a name over Socket.IO and listen for pushed mail events; mail lives in process memory for 30 minutes, so there is no persistent list to poll.

Do I need an API key or an account?

No. No accounts, no passwords, no keys. The trade-off is that the name is the only credential: anyone who knows it can subscribe to the same live mail. Send only synthetic test data.

Can I use a fixed address for deterministic tests?

Yes, with set shortid and a name of 1–32 lowercase letters, digits, dots, underscores or hyphens. Names are first come, first served, unreserved and unprotected. A name held by another live connection returns E_SHORTID_TAKEN, so use a run-specific suffix rather than one literal across parallel jobs.

What happens if my script disconnects before the mail arrives?

The next claim triggers one mail history event with the buffered messages. Deduplicate by _id, because a reconnect replays mail you may already have. A server restart clears the buffer and changes the boot id.

Can I run this in CI or self-host it?

Both. Build the image, expose port 25 directly, point an MX record at the host and run exactly one instance. Remember that many sign-up forms block known disposable domains — that rejection happens on their side, not here.

Related reading

  • Temp mail FAQ: lifetime, attachments, limits
  • What "30-minute email" actually means
  • How the service is built
  • Source code and full documentation on GitHub
TempMail.cc

No account, no password, no sign-up.

Terms of Service · Privacy Policy

© 2026 TempMail.cc

Site
Home FAQ About Changelog GitHub
Guides
30-minute email Temp mail for Facebook Temp mail for Instagram Temp mail for Discord Temp mail for Steam Temp mail for Temu For developers