TempMail OTP MCP Server
by mifdlaldev
README.md
# TempMail OTP — Self-Hosted Temporary Email Generator
Receive-only temp email server with automatic OTP extraction for security research, QA automation, and disposable identity workflows.
## Features
- **SMTP catch-all** — accepts all inbound email on your domain (port 25, no auth)
- **Real-time inbox** — SSE stream pushes new emails instantly to the browser
- **OTP extraction** — automatic detection of verification codes (4–8 digit, alphanumeric)
- **Link extraction** — parse all URLs from plain-text and HTML email bodies
- **REST API** — authenticated endpoints for email generation, reading, OTP, and link extraction
- **MCP integration** — stdio-based Model Context Protocol server for AI agents (Hermes, Claude, etc.)
- **Docker** — multi-stage build, SQLite persistence, health check included
- **Dark/light mode** — built with Tailwind CSS + shadcn/ui
- **DNS verification** — built-in `/api/dns-check` endpoint validates MX, SPF, DMARC records
- **Multi-key support** — manage API keys via the web UI or `/api/keys` endpoint
## Prerequisites
- **Node.js 20+** (with `tsx` for MCP; `npm` for package management)
- **Domain name** — configured with MX, SPF, and DMARC records
- **Port 25 access** — required for SMTP receiving; unavailable on most home ISPs. Use a VPS (Oracle Cloud Free Tier, AWS EC2, DigitalOcean) or port forwarding if supported
## Quick Start
### 1. Clone and install
```bash
git clone <repo-url> website-email
cd website-email
npm install
```
### 2. Configure environment
```bash
cp .env.example .env
```
Edit `.env` with your domain and a strong API key:
```ini
DOMAIN=mail.yourdomain.com
API_KEY=your-generated-api-key
```
Generate a secure API key:
```bash
openssl rand -hex 32
```
### 3. Set up DNS records
Configure MX, SPF, and DMARC records for your domain. See the complete guide:
→ [docs/dns-setup.md](docs/dns-setup.md)
Quick verification after DNS propagates:
```bash
dig MX yourdomain.com +short
dig TXT yourdomain.com +short | grep spf
dig TXT _dmarc.yourdomain.com +short
```
### 4. Start the servers
**Terminal 1 — Next.js web UI + API:**
```bash
npm run dev
```
**Terminal 2 — SMTP server (receives mail):**
```bash
npx tsx src/infrastructure/smtp/start.ts
```
### 5. Open the app
```
http://localhost:3000
```
Generate a temp email address, send a test email to it, and watch it appear in real-time.
## Docker Deployment
### 1. Configure `.env`
Same as Quick Start — fill in `DOMAIN` and `API_KEY`.
### 2. Build and start
```bash
docker compose up -d
```
### 3. Verify
```bash
curl http://localhost:3000/api/config
# {"success":true,"data":{"domain":"mail.yourdomain.com"}}
# Note: never returns API_KEY — paste API_KEY from .env into the UI when prompted
```
> **Note:** Docker Compose exposes port 3000 only (web UI + API). Run the SMTP server directly on the host for port 25:
```bash
npx tsx src/infrastructure/smtp/start.ts
```
For production, use a reverse proxy (nginx/Caddy) for TLS termination and run SMTP via a process manager (systemd, pm2).
## Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `DOMAIN` | Yes | — | Your domain for receiving email (e.g. `thefreelancer.web.id`) |
| `API_KEY` | Yes | — | Secret key for API, MCP, and Cloudflare Worker (must match Worker secret) |
| `SMTP_PORT` | No | `25` | Port for inbound SMTP |
| `SMTP_HOST` | No | `0.0.0.0` | Bind address for SMTP server |
| `DB_PATH` | No | `./data/emails.db` | SQLite database file path |
| `NEXT_PUBLIC_APP_URL` | No | `http://localhost:3000` | Public URL of the application |
| `CLOUDFLARE_WORKER_URL` | No | _(empty)_ | Cloudflare Worker base URL for cloud inbox fallback. Empty = local SQLite only. Example: `https://freelancer-mail.<subdomain>.workers.dev` |
> **Do not use** legacy URLs such as `tempik.email1-f03.workers.dev`. Current worker name is `freelancer-mail`. See `cloudflare-email-worker/README.md` and root `AGENTS.md`.
## REST API
Most endpoints require an `x-api-key` header matching server `API_KEY` **or** an active key created via `/api/keys`.
`GET /api/config` is public and returns `{ domain }` only (never the secret).
Response format: `{ "success": true, "data": ... }` or `{ "success": false, "error": "..." }`.
```bash
npm test # unit tests (vitest)
npm run lint
npm run build
```
### Email Addresses
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/addresses` | List all addresses |
| `POST` | `/api/addresses` | Generate new address (optional body: `{ "address": "custom@domain" }`) |
| `DELETE` | `/api/addresses/:id` | Delete an address and its emails |
### Emails
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/api/emails/generate` | Generate address (shorthand) |
| `GET` | `/api/emails/:address` | List emails for an address |
| `GET` | `/api/emails/:address/:id` | Read a specific email |
| `DELETE` | `/api/emails/:address/:id` | Delete a specific email |
| `POST` | `/api/emails/:address/:id/otp` | Extract OTP from email |
| `GET` | `/api/emails/:address/:id/links` | Extract links from email |
| `GET` | `/api/emails/:address/stream` | SSE stream for real-time email events |
### Misc
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/config` | Get server domain only (no auth, no secrets) |
| `GET` | `/api/dns-check` | Verify DNS records (MX, SPF, DMARC; requires API key) |
| `GET` | `/api/keys` | List API keys |
| `POST` | `/api/keys` | Create new API key |
### Example: Full workflow
```bash
KEY="your-api-key"
DOMAIN="mail.example.com"
# Generate an address
curl -sH "x-api-key: $KEY" -X POST http://localhost:3000/api/emails/generate | jq
# List emails
curl -sH "x-api-key: $KEY" "http://localhost:3000/api/emails/temp-abc123@$DOMAIN" | jq
# Read email and extract OTP
curl -sH "x-api-key: $KEY" -X POST \
"http://localhost:3000/api/emails/temp-abc123@$DOMAIN/eml_xxx/otp" | jq
```
## MCP Integration
The MCP server exposes 5 tools for AI agents via stdio transport:
| Tool | Description |
|------|-------------|
| `temp_email_generate` | Generate a new temporary email address |
| `temp_email_list` | List emails for an address |
| `temp_email_read` | Read a specific email by ID |
| `temp_email_extract_otp` | Extract OTP/verification code from an email |
| `temp_email_extract_links` | Extract all links from an email |
Configuration and workflow documentation:
→ [docs/hermes-integration.md](docs/hermes-integration.md) — Hermes MCP configuration
→ [docs/chrome-devtools-mcp.md](docs/chrome-devtools-mcp.md) — Browser automation with Chrome DevTools MCP
→ [docs/workflow-examples.md](docs/workflow-examples.md) — Copy-paste ready prompts
Quick test:
```bash
DOMAIN=mail.example.com API_KEY=your-key npx tsx src/infrastructure/mcp/start.ts
```
## Architecture
Two independent **inbound** paths. App reads local SQLite first, then optionally pulls from the Worker.
```
Internet mail
/ \
v v
┌──────────────────┐ ┌────────────────────────────┐
│ Host SMTP :25 │ │ CF Email Routing (zone) │
│ smtp-server │ │ → Worker freelancer-mail │
│ → local SQLite │ │ → D1 freelancer-mail-db │
└────────┬─────────┘ └─────────────┬──────────────┘
│ │
│ GET /emails/:addr (x-api-key)
│ │
v v
┌────────────────────────────────────────────┐
│ Next.js app (UI + REST + SSE) │
│ getEmailsByAddress: local first, then │
│ CLOUDFLARE_WORKER_URL if set │
│ OTP/link extraction happens HERE only │
└──────────────────┬─────────────────────────┘
│
v
MCP stdio (npm run mcp)
```
**Domain is configurable:** set app `DOMAIN` + Cloudflare Email Routing on that zone. Worker name is **not** the email domain.
**Stack:** Next.js 14 + TypeScript + Tailwind CSS + shadcn/ui + better-sqlite3 + smtp-server + mailparser + Cloudflare Workers/D1
## Cloudflare Worker (optional cloud ingest)
Separate package: [`cloudflare-email-worker/`](cloudflare-email-worker/).
| Piece | Value |
|-------|--------|
| Worker | `freelancer-mail` |
| D1 | `freelancer-mail-db` |
| Deploy | `cd cloudflare-email-worker && npx wrangler deploy` (or `./deploy-fresh.sh`) |
| App wire | `CLOUDFLARE_WORKER_URL` in `.env` + same `API_KEY` as Worker secret |
| Dashboard | Email Routing catch-all → Send to Worker `freelancer-mail` |
Details: [cloudflare-email-worker/README.md](cloudflare-email-worker/README.md), [docs/deployment.md](docs/deployment.md), [AGENTS.md](AGENTS.md).
## Additional Documentation
- [docs/README.md](docs/README.md) — **Doc index**
- [docs/architecture.md](docs/architecture.md) — Dual ingest + components
- [docs/api.md](docs/api.md) — REST + Worker HTTP
- [SECURITY.md](SECURITY.md) — Reporting + secrets policy
- [AGENTS.md](AGENTS.md) — AI / agent ground truth
- [openspec/](openspec/) — Spec-driven requirements (`openspec/specs/`)
- [docs/dns-setup.md](docs/dns-setup.md) — MX, SPF, DMARC
- [docs/deployment.md](docs/deployment.md) — Production, Docker, Worker
- [docs/troubleshooting.md](docs/troubleshooting.md) — Common issues
- [docs/hermes-integration.md](docs/hermes-integration.md) — Hermes MCP
- [docs/chrome-devtools-mcp.md](docs/chrome-devtools-mcp.md) — Browser automation
- [docs/workflow-examples.md](docs/workflow-examples.md) — End-to-end workflows
## License
MITThis server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues