A full-stack temporary/disposable email service. Users instantly generate throwaway inboxes with no account — access is a secure random token held by the browser (and shareable as a private recovery link). Mail arrives in real time over SSE, HTML is sanitized at ingest, and everything expires and self-destructs.
| Layer | Tech |
|---|---|
| Frontend | React 18 · Vite · TailwindCSS v4 · dark-mode dashboard |
| Backend | Node.js · Express · smtp-server + mailparser inbound |
| Database | SQLite (better-sqlite3, WAL) — zero-setup local dev |
| Real-time | Server-Sent Events (per-inbox stream) |
- Instant inboxes — random readable addresses (
quietfalcon83@…), multiple domains, selectable lifetime (10 min / 1 h / 24 h), live expiry countdown, one-click regenerate. - Custom & reclaimable addresses — optionally pick your own name
(
my-signups@…), and claim any expired address again to keep receiving mail there (fresh inbox + token; expired messages stay deleted; live addresses return409 ADDRESS_TAKEN; role names likepostmasterare reserved). - Real-time inbox — new mail appears instantly via SSE with a toast + row highlight; unread count in the tab title.
- Safe rendering — HTML email is sanitized server-side (scripts, iframes, forms,
javascript:links, and remote images/tracking pixels stripped) and additionally rendered in a sandboxed iframe with scripts disabled. Attachments are intentionally not stored in v1. - No login, still recoverable — inbox tokens live in
localStorage("Recent inboxes" panel); a private recovery link (/inbox/<id>#<token>) reopens an inbox on any device. The token rides in the URL fragment, so it is never sent to the server or logged. Anyone with the link can read the inbox. - Auto-expiry — configurable TTLs, extendable up to a hard max lifetime; a cleanup worker purges expired inboxes and messages; manual delete any time.
- Clean REST API — token-authenticated, rate-limited, uniform JSON errors, in-app
docs at
/docs.
npm install # installs server + client workspaces
npm run dev # starts API+SMTP (:4000, :2525) and Vite dev server (:5173)Open http://localhost:5173 — an inbox is generated automatically.
Send yourself a test email (includes hostile HTML to demo the sanitizer):
node server/scripts/send-test-email.js youraddress@tempmail.local "Hello!"It appears in the browser instantly. That's the whole loop.
Default dev domains are
tempmail.local/dropbox.local— fake TLDs that only resolve inside this system. Real mail requires a real domain + MX record (below).
Copy .env.example to server/.env and adjust. Everything is
documented inline: ports, domains, DB path, TTL options, max lifetime, message
caps, cleanup cadence, rate limits, CORS.
┌────────────┐ SMTP :25/2525 ┌──────────────────────────────────────┐
│ Internet │ ───────────────▶ │ smtp/receiver.js │
│ (senders) │ │ RCPT gate → parse → sanitize → DB │
└────────────┘ └──────────────┬───────────────────────┘
│ event bus (per inbox)
┌────────────┐ HTTPS ┌──────────────▼───────────────────────┐
│ Browser / │ ───────────────▶ │ Express API /api/* │
│ API client │ ◀─────────────── │ token auth · rate limits · SSE │
└────────────┘ └──────────────┬───────────────────────┘
│
┌──────────────▼─────────┐ ┌────────────────┐
│ SQLite (WAL) │◀──│ jobs/cleanup.js │
│ domains·inboxes·msgs │ │ purge expired │
└────────────────────────┘ └────────────────┘
Key files (each is commented where the interesting logic lives):
| Area | File |
|---|---|
| Mail receiving | server/src/smtp/receiver.js |
| HTML sanitization | server/src/lib/sanitize.js |
| Inbox token system | server/src/lib/tokens.js |
| API auth + rate limits | server/src/api/middleware.js |
| REST routes + SSE | server/src/api/router.js |
| Cleanup worker | server/src/jobs/cleanup.js |
| Schema (auto-migrating) | server/src/db.js |
| Browser credential store | client/src/lib/storage.js |
- Tokens, not accounts. Inbox ids are 12 random bytes; access tokens are 32 random bytes. Only a SHA-256 hash of the token is stored — a DB leak leaks no credentials. Comparison is constant-time.
- Receive-only, never a relay.
RCPT TOis accepted only for live inboxes on configured domains; everything else gets550. SMTPAUTHis disabled; the server has no outbound mail capability at all. - Sanitize once, at the door. Hostile HTML never reaches the database. The frontend adds a second wall: a sandboxed iframe with no script execution.
- No token leakage. The API never logs request URLs (SSE tokens travel as query
params), and recovery links carry the token in the
#fragment, which browsers do not transmit. - Abuse limits. Per-IP rate limits (general + stricter inbox-creation limit), message size cap at SMTP time, per-inbox message-count cap, JSON body size cap.
Interactive docs with copyable curl examples live at /docs in the app.
Summary:
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /api/domains |
— | List available domains |
| POST | /api/inboxes |
— | Create inbox (returns token once) |
| GET | /api/inboxes/:id |
token | Inbox metadata |
| GET | /api/inboxes/:id/messages |
token | List messages |
| GET | /api/inboxes/:id/messages/:messageId |
token | Read message (sanitized HTML) |
| DELETE | /api/inboxes/:id/messages/:messageId |
token | Delete one message |
| POST | /api/inboxes/:id/extend |
token | Extend expiry (capped) |
| DELETE | /api/inboxes/:id |
token | Delete inbox + messages |
| GET | /api/inboxes/:id/events |
token | SSE stream (?token=) |
Auth: Authorization: Bearer <token>. Errors: { "error": { "code", "message" } }
with INVALID_TOKEN 401, INBOX_NOT_FOUND 404, INBOX_EXPIRED 410,
VALIDATION_ERROR 400, RATE_LIMITED 429.
To accept mail for a real domain (say tmpbox.example):
-
Run the server somewhere reachable with port 25 open inbound (many clouds block 25 by default — request it, or use a mail-friendly host).
-
Set
SMTP_PORT=25(andDOMAINS=tmpbox.example) in the environment. On Linux, either run as root, grant the binaryCAP_NET_BIND_SERVICE, or keep the app on 2525 and redirect:iptables -t nat -A PREROUTING -p tcp --dport 25 -j REDIRECT --to-port 2525. -
DNS records for the mail domain:
Type Name Value Notes A mx.tmpbox.example<server public IP>The mail host itself MX tmpbox.example10 mx.tmpbox.exampleRoute mail to the server -
Wait for DNS propagation, then
nc mx.tmpbox.example 25should greet you with220 … TempMail inbound server. Anything sent toanything@tmpbox.examplewhose inbox exists will be delivered; unknown/expired addresses get550.
Multiple domains: point each domain's MX at the same host and list them all in
DOMAINS (comma-separated).
- Topology. Build the client (
npm run build→client/dist) and serve it from a reverse proxy (Caddy/nginx) that also forwards/apito the Node process (port 4000). Run the Node process under a supervisor (systemd, PM2, Docker). SMTP (25) goes directly to the same process. - Reverse proxy must not buffer SSE — nginx:
proxy_buffering off;for/api/inboxes/*/events(the server already sendsX-Accel-Buffering: no). - Set
CORS_ORIGINandPUBLIC_URLto your real site origin, and keeptrust proxysemantics in mind: the app trusts one proxy hop for client IPs (rate limiting). - TLS. Terminate HTTPS at the proxy. Inbound SMTP runs plaintext in v1
(STARTTLS is disabled); add a cert and enable STARTTLS in
server/src/smtp/receiver.jsif you need encrypted delivery hops. - Database. SQLite in WAL mode comfortably handles this workload on one node.
For multi-node, swap the store for Postgres and replace the in-process event bus
(
server/src/events/bus.js) with Redis pub/sub — both are isolated behind small modules for exactly this reason. - Scheduled cleanup runs in-process every
CLEANUP_INTERVAL_SECONDS; no external cron needed.
Inboxes and messages are deleted automatically at expiry (plus a short tombstone window so clients see "expired" rather than "not found"). There is no outbound email, no tracking, and no account data. Anyone with an inbox's recovery link or token can read that inbox — the UI says so wherever links are copied.
├── client/ # React + Vite + Tailwind frontend
│ └── src/
│ ├── pages/ # Home (dashboard), ApiDocs
│ ├── components/ # AddressCard, MessageList, MessageView, RecentInboxes, Toast, Layout
│ └── lib/ # storage.js (token store), time.js
├── server/
│ ├── src/
│ │ ├── api/ # router, auth middleware, error shapes
│ │ ├── smtp/ # inbound mail receiver
│ │ ├── jobs/ # cleanup worker
│ │ ├── events/ # in-process pub/sub for SSE
│ │ └── lib/ # tokens, sanitize, address generator
│ └── scripts/ # send-test-email.js
└── .env.example