Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

📮 OmniMod Temp Email — Disposable Email Service

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)

Features

  • 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 return 409 ADDRESS_TAKEN; role names like postmaster are 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.

Quick start (local development)

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).

Configuration

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.

Architecture

┌────────────┐  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

Security model

  • 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 TO is accepted only for live inboxes on configured domains; everything else gets 550. SMTP AUTH is 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.

The REST API

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.

Receiving real email: DNS / MX setup

To accept mail for a real domain (say tmpbox.example):

  1. Run the server somewhere reachable with port 25 open inbound (many clouds block 25 by default — request it, or use a mail-friendly host).

  2. Set SMTP_PORT=25 (and DOMAINS=tmpbox.example) in the environment. On Linux, either run as root, grant the binary CAP_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.

  3. DNS records for the mail domain:

    Type Name Value Notes
    A mx.tmpbox.example <server public IP> The mail host itself
    MX tmpbox.example 10 mx.tmpbox.example Route mail to the server
  4. Wait for DNS propagation, then nc mx.tmpbox.example 25 should greet you with 220 … TempMail inbound server. Anything sent to anything@tmpbox.example whose inbox exists will be delivered; unknown/expired addresses get 550.

Multiple domains: point each domain's MX at the same host and list them all in DOMAINS (comma-separated).

Production deployment notes

  • Topology. Build the client (npm run buildclient/dist) and serve it from a reverse proxy (Caddy/nginx) that also forwards /api to 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 sends X-Accel-Buffering: no).
  • Set CORS_ORIGIN and PUBLIC_URL to your real site origin, and keep trust proxy semantics 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.js if 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.

Privacy & retention

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.

Project layout

├── 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

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages