Skip to main content
Glama
mifdlaldev

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

MIT

Maintenance

ActivitySlowing
ResponsivenessNo issues