MailAgent

July 31, 2026 · View on GitHub

Important

Development has moved to navorina-labs/AgentsOS. This repository is preserved as a read-only historical snapshot; use AgentsOS for source code, issues, and releases.

Temporary inboxes for AI agents and QA/E2E: create an inbox, submit its address to a signup/login form, wait for OTP or magic link, and clean up automatically.

Workspace Agent adds supplied-thread summaries, draft replies, reminders, persistent action history, and policy-gated idempotent replies from stored inbound messages. Sending is disabled by default; calendar writes remain disabled.

Roadmap: docs/ROADMAP.md
Your own agent without our API: docs/INTEGRATE.md — self-host, MCP, REST.
For QA: docs/QA.md — label, subjectContains, callback, Playwright.

Prod: webmailagent.com · API: api.webmailagent.com · Remote MCP: https://api.webmailagent.com/mcp.

Agent responses include otp, primaryLink, primaryButton, confidence metadata, raw MIME links, attachments, diagnose hints, run timeline, and cleanup policy fields.

Stack

  • Cloudflare Workers + Hono
  • Cloudflare Queues (+ DLQ)
  • Durable Objects (SSE /events)
  • Neon Postgres
  • Resend Inbound

Full setup with secrets: SETUP.md · check: npm run setup:check

Quick start

Hosted QA / agent smoke

export MAILAGENT_API_URL=https://api.webmailagent.com
export MAILAGENT_API_KEY=ma_...

npm run doctor:qa
npm run smoke:qa

QA starter: examples/qa-pilot-starter · Cypress: examples/qa-pilot-cypress-starter · agent handoff: docs/AGENT-HANDOFF.md.

Self-host setup

1. Dependencies

npm install

2. Neon

Create a project on neon.tech, copy connection string.

cp .env.example .env
# fill DATABASE_URL
npm run db:migrate

3. Resend

  1. API key → RESEND_API_KEY
  2. Dashboard → Emails → Receiving — copy domain (xxxx.resend.app) → INBOX_DOMAIN
  3. Webhooks → event email.received → URL: https://<worker>/webhooks/resend
  4. Signing secret → RESEND_WEBHOOK_SECRET

Locally: npm run dev + tunnel (cloudflared / ngrok) to wrangler port.

4. Worker secrets

npx wrangler secret put DATABASE_URL
npx wrangler secret put RESEND_API_KEY
npx wrangler secret put RESEND_WEBHOOK_SECRET
npx wrangler secret put API_KEY
npx wrangler secret put INBOX_DOMAIN

Local dev: create .dev.vars (same keys, see .env.example).

5. Deploy

npm run deploy

First deploy creates queues mailagent-email and mailagent-email-dlq.

API

Protected /v1 endpoints require header:

Authorization: Bearer <API_KEY>
MethodPathDescription
GET/v1Discovery: endpoints, presets, MCP tools
GET/v1/agentAgent hub: tools, flows, docs, OAuth/MCP metadata
GET/v1/openapi.jsonOpenAPI 3.0 (agents)
POST/v1/agent/verifyPreferred agent verify flow with agent.primaryAction
GET/v1/agent/flowsSignup/login/reset/invite flow templates
GET/v1/agent/runs/:runId/timelineAgent-readable run timeline
POST/v1/inboxes/openOne-shot: create → wait → extract → delete
POST/v1/inboxesCreate inbox (ttlMinutes, service, expectFrom, notifyEmail, cleanup policy)
GET/v1/inboxes/:idStatus
GET/v1/inboxes/:id/messagesMessages
GET/v1/inboxes/:id/extractOTP, links, primaryButton, confidence from latest message
GET/v1/inboxes/:id/eventsSSE — wait for new message
GET/v1/inboxes/:id/wait?timeout=60Poll fallback (every 500ms)
GET/v1/inboxes/:id/diagnoseFailure recovery hints and retry payloads
POST/v1/inboxes/:id/simulateQA/dev simulated message, no SMTP required
GET/v1/inboxes/:id/search?q=Search messages
GET/v1/inboxes/:id/callbackscallbackUrl delivery log (QA)
GET/v1/inboxes/:id/notify-deliveriesnotifyEmail relay log (manual QA)
GET/v1/inboxes/:id/messages/:messageId/rawRaw MIME .eml download
GET/v1/inboxes/:id/messages/:messageId/attachmentsAttachment metadata/downloads
GET/POST/v1/domainsCustom domain DNS setup
POST/v1/emails/checkEmail check: syntax, disposable, role, MX (no SMTP probe)
DELETE/v1/inboxes/:idDelete
GET/v1/statsInbox / message counters (24h)
POST/webhooks/resendResend webhook (no API key)
GET/healthDB ping

Example

# create inbox
curl -s -X POST "$MAILAGENT_API_URL/v1/inboxes" \
  -H "Authorization: Bearer $MAILAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ttlMinutes":15,"service":"auth0","deleteAfterSuccess":true}' | jq

# SSE (another terminal)
curl -N "$MAILAGENT_API_URL/v1/inboxes/<id>/events" \
  -H "Authorization: Bearer $MAILAGENT_API_KEY"

# send mail to address from response → SSE gets event: message

Reliability

  • Webhook responds right after MAIL_QUEUE.send
  • Idempotency: messages.provider_id = Resend email_id (UNIQUE)
  • Queue retry up to 5 times → DLQ
  • Hourly cron: delete expired inboxes
  • OTP/links extracted in queue processing, not in webhook

Custom domain (prod)

In Resend: MX on subdomain inbox.yourbrand.com, INBOX_DOMAIN=inbox.yourbrand.com.

MCP for Cursor / Codex / agents

Official protocol Model Context Protocol; SDK: @modelcontextprotocol/sdk (stdio).

npm packages

Published packages (see docs/PUBLISH.md):

npm install @mailagent/mcp      # stdio MCP for Cursor
npm install @mailagent/agent    # REST + remote MCP SDK
npm install @mailagent/qa       # Playwright / Cypress QA

Local build from repo:

npm run build:mcp
npm run build:qa
npm run build:agent

Remote MCP (prod): https://api.webmailagent.com/mcp — OAuth/DCR: docs/MCP-OAUTH.md. Codex guide: docs/CODEX.md.

Build MCP server (from repo)

Add to .env (see .env.example):

MAILAGENT_API_URL=https://api.webmailagent.com
MAILAGENT_API_KEY=<same API_KEY as Worker>

Connect in Cursor

Project already has .cursor/mcp.json:

{
  "mcpServers": {
    "mailagent": {
      "command": "node",
      "args": ["mcp/dist/index.js"],
      "envFile": ".env"
    }
  }
}
  1. Cursor Settings → MCP — server mailagent should be green
  2. Click Refresh on tools list
  3. In Agent/Composer: "create inbox via mailagent" — agent will call tools

Globally for all projects: copy block to ~/.cursor/mcp.json (absolute path to mcp/dist/index.js).

Tools

ToolPurpose
mailagent_issue_accessIssue short-lived scoped key for one autonomous agent run
mailagent_start_runStart server-side run state and get the first autopilot plan
mailagent_report_runReport progress/failure and get the next plan
mailagent_next_runResume a run from saved state and get the next plan
mailagent_plan_nextAutopilot planner: returns the next tool, payload, and recovery steps
mailagent_workspace_summarizeWorkspace preview: summarize supplied mail/thread messages
mailagent_workspace_draft_replyWorkspace preview: draft reply only, never sends
mailagent_workspace_suggest_remindersWorkspace preview: suggest reminders/follow-ups
mailagent_workspace_create_reminder / list_reminders / complete_reminderWorkspace preview: persist and manage follow-ups
mailagent_workspace_log_action / list_actionsWorkspace preview: record and inspect agent action history
mailagent_workspace_get_policy / set_policyRead or configure admin-owned autonomy guardrails
mailagent_workspace_model_statusInspect DeepSeek/Qwen readiness and fallback priority
mailagent_workspace_execute_replyDry-run or execute an idempotent policy-gated reply
mailagent_suggest_presetSuggest service, expectFrom, subjectContains, and flow from a sample auth email
mailagent_verify_signupPreferred: wait and return agent.primaryAction
mailagent_create_inboxCreate inbox (service, notifyEmail, cleanup options)
mailagent_wait_and_extractCreate/wait/extract/delete one-shot flow
mailagent_wait_for_messageWait for first message (SSE, up to 120s)
mailagent_extract_verificationOTP, links, confidence, primaryButton
mailagent_extract_structuredPresets: 2fa, magic_link, invite, invoice, receipt
mailagent_diagnose_inboxTimeout/debug hints and retry payloads
mailagent_simulate_messageInject QA mail without SMTP
mailagent_list_messagesAll messages
mailagent_search_messagesSearch messages
mailagent_get_raw_messageRaw MIME
mailagent_list_attachments / mailagent_get_attachmentAttachments
mailagent_check_emailApp email validation tests only
mailagent_send_message / mailagent_list_threadsOutbound/reply and conversation view
mailagent_get_run_session / mailagent_get_run_timelineAgent run memory and timeline
mailagent_cleanup_inboxesCleanup by labelPrefix or runId
mailagent_get_inboxInbox status
mailagent_delete_inboxDelete early

Full current list: GET /v1/agent returns mcpTools (currently 38).

Agent skill: .cursor/skills/mailagent-mcp/SKILL.md

CLI (terminal / CI)

After npm run build:mcp:

# one step: inbox + wait OTP (service=dribbble)
MAILAGENT_API_URL=... MAILAGENT_API_KEY=... \
  node mcp/dist/cli.js open --service dribbble --json

# or step by step
node mcp/dist/cli.js inbox create --service dribbble
node mcp/dist/cli.js wait <inboxId> --json

service presets include github, google, auth0, gitlab, bitbucket, stripe, vercel, supabase, clerk, discord, openai, resend, firebase, and more. Discover the current list via GET /v1/agent.

For autonomous browser/QA runs, call POST /v1/agent/runs/start or MCP mailagent_start_run, execute plan.nextTool, then call mailagent_report_run after each step. If the agent loses context, mailagent_next_run resumes from saved state. When there is no active inbox flow, run planning checks reminders, action history, and the stored autonomy policy: it can select a draft, execute a guarded reply, or return workspace_waiting without repeating work.

Workspace autonomy guide: docs/WORKSPACE-AUTONOMY.md.

Workspace model routing uses the configured primary provider and automatically falls back between DeepSeek and Qwen. Check readiness with GET /v1/workspace/models or MCP mailagent_workspace_model_status; an unrestricted admin can run POST /v1/workspace/models/probe.

If the next action is unclear, call POST /v1/agent/autopilot or MCP mailagent_plan_next; it returns nextTool, nextPayload, and recovery payloads. If sender or subject hints are unclear, call POST /v1/agent/preset-advice or MCP mailagent_suggest_preset with a sample from / subject first.

One-shot (agent / CI)

curl -s -X POST "$MAILAGENT_API_URL/v1/inboxes/open" \
  -H "Authorization: Bearer $MAILAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"service":"github","timeoutSeconds":90}' | jq

MCP debugging

  • Logs: Command Palette → MCP: Show Logs
  • Do not use console.log in MCP — stderr only, otherwise JSON-RPC breaks
  • Manual check: cd mcp && MAILAGENT_API_KEY=... MAILAGENT_API_URL=... node dist/index.js

Security (allowlist)

When creating inbox pass expected sender — other mail is not stored:

{ "expectFrom": "noreply@stripe.com" }
{ "expectFrom": ["noreply@auth0.com", "auth0.com"] }
{ "allowedSenders": "github.com" }

Empty allowedSenders = accept all (dev only).

CI

  • Deploy: .github/workflows/deploy-worker.yml — secrets CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID; optional MAILAGENT_API_KEY for smoke after deploy
  • npm publish: .github/workflows/publish-packages.yml — secret NPM_TOKEN
  • Prod gate: npm run test:prod:gate; full contracts: npm run test:prod

Details: docs/CI.md · docs/PUBLISH.md

Current status

  • Agent-native PBR is implemented: diagnose recovery, confidence metadata, flow templates, run timeline, cleanup policies, HTML action extraction.
  • QA pilot kit is ready; next non-code step is candidate outreach: docs/PILOT-CANDIDATES.md.
  • No-secret handoff for another agent/session: docs/AGENT-HANDOFF.md or npm run print:agent-handoff.
  • Stripe is on hold until tax/account setup is ready.