Most "temp mail" sites are ad farms. I wanted one I could own, so I built it: a disposable inbox service where the entire server is a single Node.js file with zero npm dependencies.

Demo: https://howtageek.com

The constraint

I set one rule: no dependencies. No Express, no SMTP library, no ORM, no dotenv. Node 22 ships node:sqlite and node:net — that covers storage and SMTP. Everything else is the standard library.

That leaves three jobs:

  1. Receive mail. An SMTP server on port 2527 speaking just enough of RFC 5321 to accept a message.
  2. Parse it. MIME multipart, base64/quoted-printable bodies, headers that wrap across lines.
  3. Store and serve it. SQLite for the inbox, server-rendered HTML for the UI, a sliding TTL that deletes expired messages.

The SMTP bit

The protocol is simpler than it looks. A session is a sequence of line-based commands:

EHLO client MAIL FROM:<sender@example.com> RCPT TO:<random@yourdomain.com> DATA ...message bytes... . QUIT 

You answer with three-digit codes, buffer until the lone . line, then hand the message to the parser. The subtle parts are not the commands — they are line endings (bare LF vs CRLF), dot-stuffing (a line starting with . is escaped), and the size limits you must enforce so one bad client cannot exhaust memory.

The MIME bit

Modern mail is multipart: a plain-text alternative, an HTML alternative, sometimes attachments. Parsing means walking the header tree, reading Content-Type: multipart/alternative; boundary="...", and splitting on the boundary. Then decode the transfer encoding: quoted-printable for mostly-ASCII text, base64 for everything else.

Two things bite:

  • Folded headers. A long header continues on the next line if that line starts with whitespace. Join them before parsing.
  • Charsets. =?UTF-8?B?...?= encoded words in the Subject line.

I render the HTML body inside a sandboxed iframe with scripts disabled — the raw mail is untrusted input, and a temp-mail viewer that executes remote CSS/JS is a phishing delivery system.

The TTL

Each inbox is a session with an expiry timestamp. Every read pushes it forward (sliding TTL), and a small sweeper deletes expired messages and sessions. SQLite makes this trivial: one DELETE FROM messages WHERE session_id IN (...) per sweep.

Multi-domain and the admin panel

Domains are rows in a table. Each one gets an MX record pointing at the server; Postfix (or a raw port-25 relay) forwards every address on that domain to the embedded SMTP on 2527. Adding a domain is one form field. There is also an admin panel for ads, a Markdown blog, and settings — all server-rendered, no SPA.

Why bother self-hosting

Public temp-mail services see every confirmation email you receive — that is the business model. Self-hosting moves that trust boundary to a VPS you control. The trade-off is setup time: about five minutes (copy the folder, run it, point the MX record).

What I would do differently

  • Start with a proper SMTP state machine from day one — retrofitting error handling into a happy-path parser is painful.
  • Store raw RFC822 bytes alongside the parsed body; re-parsing is cheaper than losing data.
  • Rate-limit per IP at the SMTP layer, not the HTTP layer.

If you want to poke at it: https://howtageek.com — 17 domains, 10-minute inboxes, and the whole thing is one file. Happy to answer questions about the SMTP/MIME internals in the comments.