v0.3.1 · live on npm

Real public email,
into your terminal.

popterminal gives your agents a real @popterminal.com address that streams OTPs, magic links, and signups into a CLI, an MCP server, and a local web inbox — all from one npm install. No signup. No DNS. No Cloudflare account.

View on npm

One install. Three surfaces.

CLI. MCP server. Local web inbox.

All three ship in the same npm install -g popterminal. They share the same SQLite store and the same anonymous handle. Pick whichever fits the moment.

Command-line
popterminal · pop

The original surface. Predictable exit codes, --json on every read, wait blocks until matching mail arrives. Fits the OTP-retrieval pattern in 6 lines of shell.

shell
$ popterminal wait code \
    --subject "code" \
    --timeout 60s --json
{"text":"Code: 482913", …}
MCP server
popterminal-mcp

Stdio MCP server. Wire into Claude Desktop / Cursor / Claude Code with one config line and the agent gets wait_for_email, list_emails, read_email as native tools.

claude_desktop_config.json
"mcpServers": {
  "popterminal": {
    "command": "popterminal-mcp"
  }
}
Local web inbox
popterminal web

Three-pane browser inbox at 127.0.0.1:9090. Live updates via Server-Sent Events, sandboxed iframe for HTML rendering, raw JSON view per message, click-to-copy address.

shell
$ popterminal web
popterminal web ready at
http://127.0.0.1:9090
# opens your browser…

Why popterminal

Built for the agent loop, not the inbox.

A real domain, real MX, real mail — with the rough edges sanded down for shell-driven workflows.

Real public MX

Mail reaches you through Cloudflare's actual mail servers. GitHub, Stripe, your bank — anyone who emails you, gets through.

Anonymous handles

One command, you have an address. No signup, no DNS, no Cloudflare account. Your handle persists across restarts via a local token.

Built for the shell

Predictable exit codes. --json on every read. wait blocks until mail arrives, prints JSON, exits clean.

Sub-inboxes for free

Plus-addressing splits mail by tag — +code, +signup, +anything. One handle, infinite scoped inboxes.

Offline-safe delivery

Buffered server-side for 48 hours. Replayed when you reconnect. Ack-required protocol so nothing is lost on a crash.

Local-first storage

Mail lands in SQLite at ~/.popterminal/popterminal.db. Yours, on your laptop, queryable forever.

How it works

From sender to terminal in milliseconds.

popterminal sits between Cloudflare's MX layer and your local CLI. No public IP. No tunnel binary. No DNS.

Sender
github.com
SMTP
Cloudflare MX
popterminal.com
Catch-all
Worker + DO
hibernating WS
WebSocket
Your daemon
WS client
fs.watch
SQLite + CLI
popterminal wait

Dive in

The primitives, in three motions.

01 — Address

Start the daemon. Get an address.

The first time you run popterminal start, the CLI registers an anonymous handle, stores its token in ~/.popterminal/anonymous-token (mode 0600), and opens a persistent WebSocket back to popterminal.com.

Subsequent restarts reuse the same handle. Your address survives reboots, network changes, laptop swaps. Only popterminal handle reset rolls you to a new one.

terminal
$ popterminal start daemon started (pid 8034) $ popterminal handle show handle: dugong-3xy9 address: dugong-3xy9@popterminal.com

02 — Tag

Sub-inboxes via plus-addressing.

One handle gives you infinite sub-inboxes for free. Mail to <handle>+code@popterminal.com lands in inbox code. Mail to +signup lands in signup. No configuration.

Useful for keeping signups, OTPs, and notifications separated in tests, or for scoping a fresh inbox to each agent run.

terminal
# address per signup $ popterminal handle show --json | \ jq -r .address | \ sed 's/@/+signup-42@/' dugong-3xy9+signup-42@popterminal.com # wait for that specific tag $ popterminal wait signup-42 --json

03 — Wait

Block. Match. Continue.

popterminal wait is the primitive your agents reach for. It blocks until matching mail arrives, prints clean JSON, exits with code 0. Timeout exits with 1. No matching mail? Code 1. No daemon? Code 3.

Pipe to jq, parse the OTP, continue your script. The whole pattern fits on one line.

terminal
$ popterminal wait code \ --subject "verification" \ --timeout 60s \ --json { "from": "noreply@github.com", "subject": "Your code is 482913", "text": "Code: 482913" }

In the wild

What agents do with it.

Three patterns we see most often. Each takes under 10 lines of shell.

OTP capture

Bypass 2FA in a test run

Sign up a synthetic user, receive the verification code, complete the flow. The whole loop fits in three commands.

otp.sh
# trigger the signup, then…
$ popterminal wait code --json \
    | jq -r .text \
    | grep -oE '\d{6}'
482913
OAuth bypass

Complete a magic-link login

Receive the magic-link email, extract the URL, hit it programmatically. Agents complete passwordless login without a browser session.

login.sh
# click the magic link, headlessly
$ popterminal wait login --json \
    | jq -r .text \
    | grep -oE 'https://[^\s]+' \
    | xargs curl -L
200 OK — session established
Email pipeline

Test your transactional email

Trigger your app to send an email, capture it, assert the rendered HTML. End-to-end test without spinning up a fake SMTP.

e2e.sh
# assert the reset email renders
$ trigger-reset-flow
$ popterminal wait reset --json \
    | jq '.html' \
    | grep 'Reset your password'
"Reset your password"

Universal deliverability

Real MX. Any sender.

popterminal is plumbed into Cloudflare's actual email infrastructure. Anyone who can send to a real address — automated, transactional, human — reaches your inbox.

GitHub
Stripe
Vercel
Auth0
Supabase
Resend
Clerk
Linear
AWS
Google
Slack
Discord
GitHub
Stripe
Vercel
Auth0
Supabase
Resend
Clerk
Linear
AWS
Google
Slack
Discord
Notion
Figma
Twitter
LinkedIn
Reddit
your bank
your CRM
your CI
your APIs
your customers
anyone else
Notion
Figma
Twitter
LinkedIn
Reddit
your bank
your CRM
your CI
your APIs
your customers
anyone else

Ready to receive your first email?

One install. One command. A real address that streams mail into a SQLite-backed CLI.

View on npm