CLI Reference
claim
mails claim <name> # Claim name@mails0.com
send
mails send --to <email> \
--subject <subject> --body <text>
mails send --to <email> \
--subject <subject> --html "<h1>Hello</h1>"
mails send --from "Name <email>" \
--to <email> --subject <subject> --body <text>
mails send --to <email> --subject "Report" \
--body "See attached" --attach file.pdf
mails send --to <email> --subject <subject> \
--body <text> --reply-to <email>
inbox
mails inbox # List recent
mails inbox --mailbox <addr> # Specific mailbox
mails inbox --query "keyword" # Search
mails inbox --direction inbound # Filter
mails inbox --limit 10 # Limit (default: 20)
mails inbox <id> # View details
code
mails code --to <addr> # Wait for code (30s)
mails code --to <addr> --timeout 60
mails code --to <addr> \
--since 2024-01-01T00:00:00Z
config
mails config # Show all
mails config set <key> <value> # Set a value
mails config get <key> # Get a value
mails config path # Config file path
Config keys
| Key | Description |
mode | Operation mode: hosted or selfhosted |
domain | Email domain (default: mails0.com) |
mailbox | Your receiving address |
default_from | Default sender address |
api_key | API key for hosted mode (saved by mails claim) |
send_provider | Send provider: resend, worker, or hosted |
storage_provider | Storage provider: sqlite or remote |
worker_url | Worker URL (enables remote provider) |
worker_token | Auth token for Worker |
resend_api_key | Resend API key (not needed with worker_url) |
Environment Variables
| Variable | Description |
MAILS_API_URL | Override API base URL (default: https://mails-worker.genedai.workers.dev) |
MAILS_CLAIM_URL | Override claim page URL (default: https://mails0.com) |
Worker API Endpoints
Self-hosted: /api/*
Auth via Authorization: Bearer <AUTH_TOKEN> when configured. Mailbox passed as ?to= parameter.
| Method | Endpoint | Description |
| POST | /api/send | Send email (requires RESEND_API_KEY) |
| GET | /api/inbox?to=<addr> | List emails. Params: query, limit, offset, direction |
| GET | /api/code?to=<addr> | Long-poll for verification code. Params: timeout, since |
| GET | /api/email?id=<id> | Get email details with attachments |
| DELETE | /api/email?id=<id> | Delete email + attachments + R2 objects |
| GET | /api/attachment?id=<id> | Download attachment |
| GET | /api/me | Worker info and capabilities |
| GET | /health | Health check (no auth) |
Hosted: /v1/*
Token-scoped to a mailbox (from auth_tokens table). Same routes as /api/* but mailbox is bound to the token — no ?to= needed.
| Method | Endpoint | Description |
| POST | /v1/send | Send email from bound mailbox |
| GET | /v1/inbox | List emails for bound mailbox |
| GET | /v1/code | Wait for verification code |
| GET | /v1/email?id=<id> | Get email details |
| DELETE | /v1/email?id=<id> | Delete email |
| GET | /v1/attachment?id=<id> | Download attachment |
| GET | /v1/me | Resolve token to mailbox |
Claim (hosted only)
| Method | Endpoint | Description |
| POST | /v1/claim/start | Start a claim session. Body: { "name": "myagent" } |
| POST | /v1/claim/confirm | Confirm a claim. Body: { "session_id": "..." } |
| GET | /v1/claim/poll?session=<id> | Poll claim status (used by CLI) |
Webhook Payload
When a mailbox has a webhook_url configured in auth_tokens, each received email triggers a POST request. Signed with WEBHOOK_SECRET via HMAC-SHA256 in the X-Signature header.
{
"event": "email.received",
"mailbox": "agent@mails0.com",
"email": {
"id": "msg_abc123",
"from": "sender@example.com",
"to": "agent@mails0.com",
"subject": "Your verification code",
"text": "Your code is 847291",
"html": "<p>Your code is 847291</p>",
"date": "2026-03-27T10:00:00Z",
"has_attachments": false
}
}
SDK Usage
import { send, getInbox, searchInbox,
getEmail, waitForCode, deleteEmail
} from 'mails-agent'
await send({
to: 'user@example.com',
subject: 'Hello',
text: 'World'
})
const emails = await getInbox(
'agent@mails0.com', { limit: 10 }
)
const results = await searchInbox(
'agent@mails0.com', { query: 'invoice' }
)
const code = await waitForCode(
'agent@mails0.com', { timeout: 60 }
)
await deleteEmail('email-id')
Self-Host on Your Domain
Deploy on Cloudflare (free tier). Full control, no third-party dependency.
Prerequisites
| What | Why | Cost |
| Domain on Cloudflare | Email address agent@yourdomain.com | You own it |
| Resend account | SMTP delivery | Free 100/day |
Deploy
git clone https://github.com/Digidai/mails
cd mails/worker && bun install
# Create D1 database
wrangler d1 create mails
# Paste database_id into wrangler.toml
# Initialize schema
wrangler d1 execute mails \
--remote --file=schema.sql
# (Optional) R2 for large attachments
wrangler r2 create mails-attachments
# Set secrets
wrangler secret put RESEND_API_KEY
wrangler secret put AUTH_TOKEN
# Deploy
wrangler deploy
Configure Email Routing
In Cloudflare Dashboard → your domain → Email → Email Routing:
- Enable Email Routing (adds MX records)
- Routing rules → Catch-all → Send to Worker → select your worker
- Enable the catch-all rule
Configure the CLI
mails config set mode selfhosted
mails config set worker_url \
https://your-worker.workers.dev
mails config set worker_token YOUR_AUTH_TOKEN
mails config set mailbox agent@yourdomain.com
mails config set default_from agent@yourdomain.com