openagentemail
About
Self-hosted email for AI agents: unlimited mailboxes on your own domain, with OTP extraction.
Details
- Author
- openagentemail
- Categories
- Communication, Automation, AI
Jump to
Setup
Install openagentemail in your MCP client (Claude Desktop, Cursor, Windsurf, and others).
Repository: https://github.com/openagentemail/openagentemail
Follow the installation instructions in the repository README, then restart your MCP client.
Self-hosted email for AI agents: unlimited mailboxes on your own domain, with OTP extraction.
Self-hosted email for AI agents. The open-source alternative to AgentMail.
openagent.email· website:openagentemail/website
Onedocker compose upon your own VPS gives every agent you run unlimited real mailboxes on your own domain — over REST and MCP — with OTP and verification-link extraction built in. No per-inbox pricing, no third party ever seeing your mail.
A guided wizard: it checks what you already have, helps you pick a VPS and a domain if you're missing either, and connects your agent clients (Claude Code, Cursor, Kimi Code…) once the server is up.
The manual path needs a VPS with outbound/inbound port 25 open and a domain you control:
git clone https://github.com/openagentemail/openagentemail.git && cd openagentemail cp .env.example .env # set DOMAIN, API_KEYS, mailbox password, and NTFY_ADMIN_PASSWORD docker compose up -d sudo ./deploy/dns-records.sh # prints the exact DNS records to create
doctor.shchecks.envpermissions; MX, A, SPF, DKIM, and DMARC; PTR; outbound port 25; DNS blocklists; TLS certificates on 465 and 993; and the server-side ntfy verification endpoint. It does not log in over IMAP/SMTP or send a round-trip test. Docker Mailserver can createdocker-data/as root, so usesudofor these two scripts; an EACCES failure prints the same retry instruction instead of pretending the DKIM key is missing.
If no SMTP relay is configured and outbound port 25 is blocked, APIqueued:trueonly means the local mailserver accepted the message. It doesnotmean the recipient received it: Postfix can retain the message in its queue. Treat doctor's outbound-port-25 result as the delivery prerequisite, or configure a relay before relying on direct delivery.
Public TLS with Let's Encrypt (opt-in)
The defaultdocker compose up -dpath remains self-signed: it does not start or pull Certbot and does not publish TCP 80. To use a publicly trusted mail certificate, opt in only aftermail.$DOMAINhas an A (and, if used, AAAA) record pointing at this host and the firewall permits inbound TCP 80. HTTP-01 cannot create those DNS or firewall prerequisites for you.
In.env, set the following (use a reachable contact address outside this mailserver when possible):
SSL_TYPE=letsencrypt SSL_DOMAIN=mail.example.com # exactly mail.$DOMAIN LETSENCRYPT_EMAIL=admin@example.net # optional, but recommended
First issue the certificate with the explicitly enabled sidecar; do not start the mailserver inletsencryptmode before this succeeds:
docker compose --profile letsencrypt-bootstrap up -d certbot-bootstrap docker compose logs -f certbot-bootstrap # Wait for “Successfully received certificate”, then confirm: docker compose --profile letsencrypt-bootstrap run --rm --no-deps \ --entrypoint ls certbot-bootstrap -l \ /etc/letsencrypt/live/mail.example.com/fullchain.pem \ /etc/letsencrypt/live/mail.example.com/privkey.pem
This temporary container reads the shared certificate volume, so confirmation still works after the one-shot bootstrap has stopped.
If first issuance fails, Certbot stops instead of retrying the ACME request in a tight loop. Correct the DNS/port-80/domain prerequisite, then explicitly run the samedocker compose --profile letsencrypt-bootstrap up -d certbot-bootstrapcommand again.
The entire/etc/letsencrypttree is a persistent named volume shared with the mailserver read-only: Certbot'slive/files are symlinks intoarchive/, so mounting onlylive/is incorrect. Once the first certificate exists, start the full opt-in stack and verify the public endpoints. Do not enableletsencrypt-bootstrapandletsencrypttogether: both publish host TCP 80.
docker compose --profile letsencrypt up -d ./deploy/doctor.sh openssl s_client -connect mail.example.com:465 -servername mail.example.com </dev/null \ 2>/dev/null | openssl x509 -noout -issuer -subject -dates openssl s_client -connect mail.example.com:993 -servername mail.example.com </dev/null \ 2>/dev/null | openssl x509 -noout -issuer -subject -dates
After bootstrap, the renewal sidecar runsrenewevery 12 hours and restarts with Docker. docker-mailserver's change-detection service watchesSSL_TYPE=letsencryptcertificate updates and reloads Postfix and Dovecot, so renewed certificates take effect on 465/993 without a manual container restart. Keep theletsencryptprofile enabled for normal operation. If it is omitted, the sidecars and TCP 80 are absent and the original self-signed path is unchanged.
Create an identity and hand your agent its scoped token (shown once):
curl -X POST http://localhost:3100/v1/identities \ -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \ -d '{"name":"signup-bot"}' # → 201 {"address":"fox-k7d2@example.com","name":"signup-bot","token":"oa_…"}
The API binds to127.0.0.1by default — reach it from other hosts over an SSH tunnel or a TLS proxy:docs/security.md.
Paths are exact: call/v1/notify, not/v1/notify/— a trailing slash returns a plain 404 rather than the API error format.
Already have a mail provider for your domain? Run the API by itself withcompose.api-only.yaml, connected to that provider's catch-all mailbox. Theexternal mail server guidecovers the required catch-all setup, Portainer deployment, SMTP sender limits, and TLS certificate verification.
HTML email is treated as hostile input. The UI removes scripts, images, forms, links, sender CSS, and all attributes except numeric table spans, then loads the result in a separately sandboxed frame with a restrictive CSP. Thesanitize-html2.x dependency is deliberately pinned to an exact version; upgrade it in a dedicated change and rerun the full poison-message corpus.
An admin session lands onOverview: every identity in one table with the message count, unseen count, last delivery, and creation day, plus totals across the top. Each row also shows whether the identity has a token (green dot) and hasRotateandDeleteaction buttons. ACreate Identitybutton above the table opens a form where you can set a custom address prefix (e.g.qa-bot) or leave it blank for a random one; the new token is shown once in a copy-to-clipboard modal. Identity sessions never see the overview or management controls — they go straight to their own inbox. The page is served from the same in-process API as the rest of/ui; there is no new public endpoint outside/ui/api.
What the numbers mean, and where they stop:
- Counts are a window, not a lifetime total.One scan reads the newest 500 messages in the catch-all mailbox and attributes each one to the identities it was delivered to. The header saysnewest N of M in the mailboxso the window is never mistaken for history. A message addressed to two identities counts once for each row and once — not twice — in the totals.
- Honest instead of round.Messages with enormous recipient lists can exceed the scanner's per-message and global memory bounds. Rows the scanner could not fully account for show≥NorUnknownrather than a confident wrong number, the page explains why, andunmatchedInWindowis reported asnullinstead of a made-up zero. The same applies to an identity created after the last scan: it readsUnknownuntil the next one, never a false0.
- Snapshots are cached in memory for 15 secondsand reused for up to 10 minutes while a refresh runs in the background, so opening Overview or walking in and out of inboxes does not hammer IMAP. Refresh is floored at 5 seconds. Restarting the API drops the cache — the first request afterwards pays for a fresh scan.
- Failures cool down and never lie.If a scan fails, the next attempt waits 5 seconds (the API sendsRetry-After), the table keeps showing the previous numbers, and the header says the last refresh failed. Once a snapshot is older than 10 minutes it is not revived by a failed refresh: the page reports the counts as unavailable instead of showing stale data as current. While a cold scan is still running the endpoint answers202and the address list renders immediately withLoading…in the count columns.
- GET /ui/api/overview(browser session only, admin only) returns exactly the fields the page renders — never message content.?refresh=1asks for a new scan, subject to the 5-second floor and the failure cooldown.
Deliberate limits, so nothing here is a surprise later:
- Overview shows counts and timestamps only. Subjects, senders, and verification codes need per-message parsing, which is what the inbox view is for.
- New mail can be up to 15 seconds late on the page;Refreshfetches sooner. There is no steady-state polling: the page only schedules a follow-up while counts are loading or a refresh is pending, capped at 15 attempts over 20 seconds and paced by the server's own retry hint.
- A scan that misses its deadline is abandoned even if IMAP answers a moment later, and the next request scans again. The deadline covers connecting as well as fetching, so a hung server does not park a request behind IMAP's own 30-second socket timeout.
- Up to 200 identities render in one pass. Beyond that, expect to want paging or virtual scrolling; filtering and sorting happen in the browser today.
- The dashboard self-hosts the Satoshi webfont (/ui/fonts/, the same typeface as the website) so it renders identically on every machine;font-srcis'self'. The favicon is an SVG (/ui/favicon.svg) so it needs no build step, and/ui/favicon.icokeeps returning 204 as before.
- Form controls use a dedicated--line-controlborder token so their outlines stay above 3:1 contrast. It is the one intentional deviation from the website's palette and is a one-line revert.
- Unlimited identities— one catch-all mailbox, unlimitedanything@yourdomainaddresses. No provisioning, no per-inbox cost.
- Scoped tokens— every identity gets its own token that can only read and send as that address. The admin key never has to touch your agents.
- REST + MCP— the same operations over a plain HTTP API and a first-class MCP server your agents can call directly.
- Server-side notifications— private ntfy transport for human alerts and managed-agent wake-ups, with OTP-only mail notifications by default. Topics and ntfy credentials stay on the server; phone setup is deliberately a later v0.3.1 step. Per-identitypush content tierscontrol how much of each mail-arrival alert leaves the server: tier 1 interrupt only (default), tier 2 adds subject/from, tier 3 (admin + explicit risk confirm) adds a short body preview and extracted OTP codes/links.
- mail_wait_for/POST /v1/messages/wait— long-poll an inbox until a matching message arrives, with OTP codes and verification links already extracted. Built for automated signups.
- Read/unread state—mail_mark_seen/POST /v1/messages/:id/seenlets an agent (or the human in the dashboard) mark a message handled, so the unseen count means "still needs attention". An identity may flag mail it receivedor that this server actually sent(TO ∨ trusted Sent: From match and Message-ID in the outbound registry), matching the Sent folder (#26 PR 2); a forged From does not count. It cannot flag another identity's mail. Reading a message never changes the flag by itself.
- Web dashboard for humans— inspect identities and messages at/ui(Inbox is the default landing for every session). Inbox is a three-pane mail client (identity/folder, list, detail) with Rendered / Plain text / Source; HTML stays in a sandboxed iframe. Real/ui/History routes cover Overview, Tasks, Notifications, and Configure; the shell stays a zero-bundler/ui/styles.css+/ui/app.jspair.
- Safety rails built in— per-identity send rate limits (20/hour default), automatic mail retention (30 days default), localhost-only API binding.
- Bring your own relay— send directly from the VPS, or route outbound through Amazon SES / SMTP2GO / any SMTP relay with one env var.
- DNS wizard + doctor—deploy/dns-records.shgenerates your exact DNS records;deploy/doctor.shdiagnoses deliverability before your agents depend on it.
- Single dependency: Docker.The stack is the API,docker-mailserver, and a private ntfy container. Nothing else.
┌─────────────┐ MCP (stdio) ┌──────────────────┐ │ AI agents ├──────────────────▶ │ │ (Claude Code,│ │ openagent api │ │ Cursor, …) │ REST /v1/ │ (Node, imapflow │ └─────────────┘──────────────────▶ │ + nodemailer) │ └────────┬─────────┘ │ IMAP + SMTP (localhost) ┌────────▼─────────┐ SMTP 25 │ docker-mailserver│ ◀──────────▶ the world │ catch-all mailbox│ (or your relay: SES, …) └──────────────────┘
One catch-all account on your domain receives everything. The API logs into it over IMAP, matches messages to identities by theTo/Delivered-Toheader, and sends via SMTP with theFromrewritten to the chosen identity. Polling + IMAP IDLE for low-latency waits.
Requires Node.js 18+ on the machine running the MCP client — no install step,npxdownloads and runs the package on first use.
claude mcp add openagentemail \ --env OPENAGENTEMAIL_API_URL=http://localhost:3100 \ --env OPENAGENTEMAIL_API_KEY=oa_your-identity-token \ -- npx -y @openagentemail/mcp
Or the raw JSON config (Claude Desktop, Cursor, Kimi Code):
{ "mcpServers": { "openagentemail": { "command": "npx", "args": ["-y", "@openagentemail/mcp"], "env": { "OPENAGENTEMAIL_API_URL": "http://localhost:3100", "OPENAGENTEMAIL_API_KEY": "oa_your-identity-token" } } } }
Full per-client setup (Claude Code, Claude Desktop, Cursor, Kimi Code, generic):docs/mcp-clients.md· server details:packages/mcp/README.md
- Privacy— OTP codes and verification links are credentials. Self-hosted, they never leave a machine you own. No third party reads, stores, or trains on your mail.
- Cost— a $5 VPS and a domain you already have vs. per-inbox/per-message SaaS pricing that scales linearly with your agent fleet.
- Control— your IPs, your reputation, your retention. No rate limits, no account suspensions, no sudden API deprecations.
Measured on our own production instance, idle:~190 MB RAM total, ~0% CPU, and ~2 GB of disk for the Docker images. Mail itself is a rounding error — retention auto-deletes after 30 days.
That's a $5/mo VPS — or a $10–15/yeardeal box. The real prerequisite isn't size, it'sport 25: AWS, GCP, Azure, DigitalOcean and Vultr block it by default (some unblock on request). Check before you buy — or route outbound through arelayand you don't need port 25 out at all.
Push tiers 2/3 may relay subject/from or body/OTP via ntfy (off by default). †AgentMail Outposts: email content stays in your AWS account; AgentMail still runs dashboard, auth, billing, and upgrades. BYOC ≠ open-source self-host on any VPS.
- v0.1— REST + MCP, catch-all identities,wait_forwith OTP/link extraction, DNS wizard + doctor, optional SMTP relay.
- v0.2— scoped per-identity tokens, send rate limits, automatic retention, localhost-safe defaults, expanded OTP corpus.
- v0.3 (current)— built-in private ntfy notifications, OTP-aware IMAP watcher, server-side agent wake-ups and notification ACLs. Phone delivery and webhooks are intentionally out of this first release.
- Distribution— planned one-click app in theOpenShipcatalog.
- docs/api.md— REST API reference with curl examples
- docs/security.md— tokens, exposure, rate limits, retention
- docs/mcp-clients.md— MCP setup for every major client
- docs/agent-signup.md— let agents finish sign-ups that email a code, with a verified OKX wallet example
- docs/dns-setup.md— DNS records, explained one by one
- docs/deliverability.md— the field guide to actually landing in the inbox
Issues and PRs welcome — seeCONTRIBUTING.md.
Self-hosted email and SMS platform for AI agents — each agent gets a real email address, phone number, inbox, and API key.
Email infrastructure for AI agents — create mailboxes, send/receive emails, and auto-extract verification codes.
Credit-based email infrastructure for AI agents with inboxes, sending, memory, recovery, and MCP access.
Give AI agents their own local mailbox to read, send, and triage email.
Email for AI agents. Send and receive as markdown with human oversight.
Sendmux serves AI-native startups, SaaS teams, automation agencies, and platform builders that need AI agents to send, receive, route, and react to email in production. Primary buyers and users include technical founders, founding engineers, backend engineers, platform engineers, AI automation consultants, support automation leads, ops teams, and growth teams using their own Gmail, Outlook, SMTP, or managed Amazon SES sending setup.
The MCP email server with human approval — AI drafts emails, you approve before they send. Works with Claude Code, Cursor, Windsurf, and any MCP client.
AI personal assistant for email Inbox Zero
Enables seamless integration with communication platform that allows you to reach your customers globally across any channel.
Sign in to leave a review
Use Google, GitHub, or an email account so ratings stay tied to real people.
No reviews posted yet.


