mcp-server
MCP serverCommunicationA community-built email gateway that gives your AI its own authenticated way to send and receive mail. Once added, your AI gets a dedicated inbox, outgoing messages carry verified sender authentication, and a person can be required to approve anything before it is sent.
Available today. Use it from your connected AI after setup.
Needs your own E2A account. Credentials stay encrypted.
After adding it, set up your AI's inbox and decide who needs to approve outgoing messages. Then give your AI a task that involves email.
Then ask your AI: use mcp-server
What your AI can do with it
- Send email from its own address with standard sender verification (SPF and DKIM)
- Receive mail in a dedicated inbox reserved for your AI
- Ask a person for approval before an outgoing message is sent
- Keep its email activity separate from your personal inbox
From the project's README
As published by mnexa-ai/e2a in README.md.
The open-source email API for applications and AI agents.
Send transactional email and give agents real two-way inboxes, with people in control.
We run the email infrastructure. You connect your app or agent.
Self-host with Docker · Documentation · Agent quickstart · Examples
A Token Canopy product · Apache 2.0
Using a coding agent? Paste this prompt into its chat:
Connect this coding agent to hosted e2a MCP at https://api.e2a.dev/mcp and help me sign in via browser OAuth.
Supports coding agents with remote MCP and browser OAuth. Client setup guide.
Choose how to start
- Hosted — recommended for getting started. Sign up at e2a.dev. Includes the shared
agents.e2a.devdomain for instant slug-based onboarding (no DNS setup), a dashboard, the hosted MCP server, and managed deliverability. - Self-host — run your own infrastructure. See Self-host (Docker) and Deployment. Nearly every feature works the same (content screening is currently self-host-only — see the note below); the shared-domain slug shortcut just needs you to point a mail domain at your relay and set
shared_domaininconfig.yaml.
For application email, start with the transactional email guide. For coding agents, use the prompt above or the setup instructions below.
Quickstart
Connect your agent to hosted e2a
Give your AI agent an inbox directly. Install the e2a plugin — it registers the hosted MCP server and an operate-well skill, so your agent can send, receive, reply in-thread, and hold mail for review out of the box. On first tool use it runs an OAuth flow in your browser — no API key to paste.
Claude Code
claude plugin marketplace add tokencanopy/e2a
claude plugin install e2a@e2a
Codex
codex plugin marketplace add tokencanopy/e2a
Then launch codex, run /plugins, and install e2a.
Cursor — add the MCP server directly. Put this in .cursor/mcp.json (or ~/.cursor/mcp.json to get it in every project); Cursor opens your browser to authorize on first use, no API key to paste:
{
"mcpServers": {
"e2a": { "url": "https://api.e2a.dev/mcp" }
}
}
Other MCP clients (Zed, Goose, Windsurf, Claude Desktop, raw mcp.json) — point straight at https://api.e2a.dev/mcp; ready-to-paste configs are in plugins/e2a/clients/. See plugins/e2a/README.md for the full per-client guide.
What e2a provides
e2a is the open-source email API for applications and AI agents. Any product can send transactional email over HTTP, TypeScript, or Python; agent-native systems can also use real two-way inboxes. Inbound mail arrives with structured SPF, DKIM, and DMARC evidence, and outbound mail can use an optional human-in-the-loop approval gate. Use the hosted service or run the Apache-2.0 stack yourself. No AI agent or agent framework is required for application-triggered sending.
Four ways to plug an agent in:
- MCP — point any MCP-aware runtime at the hosted server (
https://api.e2a.dev/mcp) and your agent gets an inbox toolset (list_messages,send_message,reply_to_message, …). The fastest path for agent frameworks. → MCP server - SDKs — TypeScript (
@e2a/sdk) and Python (e2a) clients with one-call webhook verification and a WebSocketlisten()stream. → SDKs - Raw delivery — subscribe a webhook, open a WebSocket, or poll the REST API directly. → Delivery channels
- CLI —
e2a listenbridges inbound mail to a local HTTP handler (including an OpenAI Responses auto-reply mode). → CLI
What you get on top of bare SMTP:
- Authenticated inbound identity — normalized SPF, DKIM, and DMARC evidence, with an explicit aligned DMARC verdict
- No public URL required — WebSocket, REST polling, and MCP all work from a laptop or behind a firewall
- Outbound API — agents send to other agents (SMTP relay) or humans (upstream SMTP, e.g. SES, Resend)
- Human in the loop — opt-in approval gate that holds outbound mail until a reviewer approves via dashboard, magic-link email, the MCP tools, or the API
- Inbound threat screening — opt-in content scan flags prompt-injection payloads (hidden HTML, Unicode-tag smuggling, encoded text) — and, with the LLM detector, phishing — then routes each message to allow · review · block, feeding the same review queue as HITL → Content screening. Available on self-hosted deployments; not yet enabled on the hosted service.
- Email reply topology — standards-compliant reply headers plus optional beta
thread_idmetadata on message reads; caller-ownedconversation_idremains application correlation - Email templates (beta) — reusable
{{variable}}templates rendered server-side at send time, plus a pre-built starter catalog → docs/templates.md - Contacts & outreach (beta) — account-level contact identity (CRUD + bulk import with safe reversal) and per-agent outreach state with server-derived reply/delivery facts, plus the
contact.duedue-queue notification event → docs/api.md - Scheduled sending (beta) —
send_aton send/reply/forward defers submission up to 90 days ahead; a scheduled send is durable acceptance (status=scheduled) and can be canceled by trashing the message before submission
What you can build
e2a is useful anywhere an AI agent needs a real inbox, persistent email threads, and a safe path from inbound message to outbound action. Start with a complete example:
-
Build an AI support agent — triage customer requests, retrieve context, reply in-thread, and hold sensitive replies for approval.
-
Build an AI receptionist — answer common inquiries, route messages, and forward conversations to a human team.
-
Build an AI scheduling agent — coordinate participants, propose times, and preserve state across multi-turn replies.
-
Build an e-commerce agent — answer order questions, handle returns, and coordinate with vendors through persistent email threads.
-
Build a sales agent — qualify inbound interest, personalize follow-ups, and keep every conversation moving.
-
Build a recruiting agent — coordinate candidates, schedule interviews, and keep hiring workflows in one thread.
-
Build a voice follow-up agent — turn a completed call into an email follow-up and keep the conversation going.
-
Build a procurement agent — coordinate quotes, purchase orders, and vendor threads with human approval for commitments.
-
A support inbox for your AI assistant — give an agent
support@yourbrand.com, receive customer mail with SPF/DKIM/DMARC already evaluated as structured evidence, and reply in-thread. Add HITL so anything sensitive waits for a human to approve before it goes out. -
Email between agents — yours and other organizations' — every agent has a real, verified address, so two companies' agents can exchange mail the same way humans do: no new client, protocol, or shared platform to install.
-
A personal concierge on your laptop — subscribe over WebSocket (no public URL, no ngrok, no port forwarding), so a local agent can watch an inbox from behind any firewall, triage it, draft replies, and take actions.
-
Email-triggered workflows — turn inbound mail into structured events: order confirmations, forms, support tickets, notifications, and receipts rendered server-side from templates, with
conversation_idcorrelated back to your app's state. -
Autopilot with human oversight — an agent drafts outbound mail (newsletters, outreach, reports) and holds each send for one-click approval via magic-link email or the review queue, with automatic expiry policy if no one reviews in time.
Working examples
e2a-runbooks builds several of the above as small, complete, runnable projects — one per agent framework, each demonstrating a different use case and a different part of this API:
| Example | Framework | What it shows |
|---|---|---|
| Support agent | Mastra | Threading, memory, and the outbound approval gate |
| Receptionist | OpenAI Agents SDK | forward to a desk allowlist, update_labels |
| AI SRE | Claude Agent SDK | Authentication as a gate; a no-tools agent that can't touch prod |
| Contract review | LangChain | Attachments — including the data-vs-download_url split |
| Escalation desk | CrewAI | Several agent identities; cross-identity conversation_id |
| Scheduling secretary | Pydantic AI | Multi-turn state rebuilt from conversations, no database |
They are examples to copy from rather than services to deploy — each says what it simplifies. For the smallest possible signed-webhook reference instead, see the in-repo minimal examples.
How it works
Human (Gmail/Outlook) · another e2a agent
│ ▲
inbound │ │ outbound
SMTP ▼ │ upstream SMTP (to humans) / relay (to agents)
┌───────────────┐
│ e2a relay │ ← MX for your agent domain points here
│ │
│ inbound ↓ │ ← evaluate SPF/DKIM/DMARC · deliver
│ outbound ↑ │ ← optional HITL hold · send
└───────────────┘
│ ▲
deliver │ │ send · reply · forward (HTTP API)
▼ │
┌───────────────┐
│ your agent │ ← webhook / WebSocket / REST poll / MCP · SDK · CLI
└───────────────┘
Inbound flow: SMTP → SPF/DKIM/DMARC evaluation → agent lookup → webhook / WebSocket / REST / MCP delivery.
Outbound flow: API call → optional HITL hold → SMTP relay (agent-to-agent) or upstream SMTP (agent-to-human).
Concepts
Delivery channels
Inbound mail reaches you several complementary ways — chosen per integration, not set on the agent. There is no delivery "mode" on the agent record; any agent the caller owns can be consumed over any of these:
| Channel | How | Public URL needed? |
|---|---|---|
| Webhooks | Account-level subscriptions (POST /v1/webhooks) — HTTPS POST per event, filterable by agent / application conversation / event type | Yes |
| WebSocket | Per-agent real-time notification stream (/v1/agents/{email}/ws) + REST fetch | No |
| REST polling | Pull messages via GET /v1/agents/{email}/messages — the default path for MCP-based agents | No |
| MCP tools | The e2a MCP server's inbox tools (list_messages, get_message, get_attachment, list_conversations, …) layered over the REST API | No |
Notifications carry lightweight metadata (message id, sender, subject); you fetch the full body + attachments over REST when you want them. A disconnected WebSocket client accumulates "unread" messages; on reconnect, the server drains them as notifications.
Webhooks are an account-level resource (/v1/webhooks), chosen per integration rather than configured on the agent.
Inbound authentication
Inbound messages expose header_from (the parsed RFC 5322 From address), envelope_from (SMTP MAIL FROM), verified_domain (a nullable DMARC-pass convenience projection), and—on detail responses—authentication (SPF, every DKIM signature, and the aligned DMARC result). Reply-To remains separate and never replaces header_from.
For list and review decisions, a non-null verified_domain means DMARC passed for that RFC 5322 From domain. On detail responses, the equivalent check is authentication?.dmarc.status === "pass". Neither field authenticates the mailbox local part, a person, or message content. authentication is null for outbound messages and providerless local loopback delivery.
Before trusting any webhook field, verify the delivery envelope's X-E2A-Signature with the webhook's whsec_… signing secret. The envelope signature covers the complete structured payload, including authentication.
The one-call shortcut parses and verifies a delivery, returning a typed event — use it instead of trusting any field on an unverified payload:
For small signed-webhook references using the ergonomic inbound facade, see the minimal Python and TypeScript OpenAI examples with provider snippets.
from e2a.v1 import construct_event, E2AWebhookSignatureError
# raw request body + the X-E2A-Signature header + your whsec_… secret
try:
event = construct_event(request_body, signature_header, webhook_secret)
except E2AWebhookSignatureError:
abort(400) # bad signature — reject the delivery
if event.type == "email.received":
email = await client.inbound.from_event(event)
print(email.envelope_from, email.verified, email.reply_targets)
result = await email.reply({"text": "Got it"})
if result.status == "pending_review":
notify_human(result.message_id)
import { constructEvent, E2AWebhookSignatureError } from "@e2a/sdk/v1";
let event;
try {
event = constructEvent(req.body, req.header("X-E2A-Signature")!, webhookSecret);
} catch (err) {
if (err instanceof E2AWebhookSignatureError) return res.status(400).end(); // bad signature
throw err;
}
if (event.type === "email.received") {
const email = await client.inbound.fromEvent(event);
console.log(email.envelopeFrom, email.verified, email.replyTargets);
const result = await email.reply({ text: "Got it" });
if (result.status === "pending_review") notifyHuman(result.messageId);
}
construct_event / constructEvent checks that the HMAC matches the canonical signing string and the timestamp is within a 5-minute replay window. Pass an array of secrets to accept either during a rotation: constructEvent(body, header, [oldSecret, newSecret]).
Messages fetched over an authenticated channel — client.messages.get(address, id) or the client.listen(...) stream — are already trusted (the bearer token authenticated the call), so no verify step is needed there.
Email threads and application conversations
Email clients build reply threads from the RFC Message-ID, In-Reply-To,
and References graph. Use reply with the original e2a message ID so e2a can
emit those headers; a fresh send or forward starts a new email thread.
conversation_id is separate, caller-owned application correlation. Both
send and reply accept the optional opaque value, and e2a keeps its existing
minting, inheritance, and delivery-correlation behavior when it is omitted.
Applications can use it to associate mail with a workflow, ticket, or model
session, but reusing it does not join fresh sends into one email thread, and
changing it does not split replies out of their RFC thread.
Existing message list and detail responses may also include thread_id
(threadId in TypeScript and SDK-shaped CLI JSON). This optional beta field is
server-owned, read-only, scoped to one agent mailbox, and derived from reply
topology. It is omitted for legacy rows without an assignment. There is no
thread_id request field, message filter, or thread list/detail endpoint, and
the field is not added to webhook events, WebSocket notifications, exports, or
MCP output.
Agent frameworks should bind model memory with conversation_id: create or
resume the runtime's internal conversation, pass its stable, non-sensitive
session ID (or an opaque stored alias), and scope that binding to the inbox and
sender. Continue replying by the original message ID—the correlation value
aligns application state, while RFC reply headers preserve the Gmail/Outlook
thread. Never treat either identifier as authorization.
Content screening
Inbound email is a prime indirect prompt-injection vector — a message can smuggle instructions aimed at your agent's LLM (hidden HTML, zero-width / Unicode-tag text, encoded payloads) or phish the human behind it. Opt in per agent and e2a inspects message content — subject, plaintext, and both visible and hidden HTML — before your agent ever sees it.
A built-in, dependency-free heuristics detector flags prompt-injection, jailbreak, obfuscation, and data-exfiltration patterns (mapped to OWASP LLM01 / MITRE ATLAS); an optional LLM detector adds semantic injection and phishing classification. Each message gets a verdict — allow · review · block — set by the agent's scan sensitivity (off · low · medium · high): review routes it into the shared HITL queue, block drops it before delivery. Screening is fail-safe — if a detector times out or degrades, the message fails to review, never to a silent allow — and every verdict is written to protection_events for audit and threshold tuning.
Turn it on with PUT /v1/agents/{email}/protection (the same sub-resource as HITL holds), which carries the inbound/outbound × gate/scan posture.
How well does it work? The detection layers are benchmarked in the open: e2a-bench — the evaluation artifact behind the EMNLP 2026 System Demonstrations paper — renders one injection payload across eight structural-hiding surfaces (hidden HTML, multipart mismatch, quoted threads, headers, encodings, PDF attachments) and scores the heuristics engine, open-source classifiers, commercial security APIs, and LLM judges on the same corpus. The per-message judge scores are committed, so the paper's tables reproduce from the artifact alone.
[!NOTE] Content screening is currently available on self-hosted deployments only — it is not yet enabled on the hosted service at e2a.dev. Self-host the image (see Deployment) and set
E2A_CONTENT_SCAN_ENABLED=true(off by default even self-hosted — see docs/deployment.md) to use it today.
Human in the loop (HITL)
When an agent's protection config holds an outbound message for review, send and reply calls do not dispatch immediately. The message is stored with status pending_review and the API returns HTTP 202 Accepted. A reviewer must approve it before delivery; otherwise, after a configurable TTL, the protection config's holds.on_expiry decides the terminal: approve (the message just goes out, terminal status sent — for outbound, approving is sending) or reject (discard, review_expired_rejected). (Inbound messages can be held for review too — there, the auto-approve terminal is review_expired_approved, releasing the message to the inbox.)
Reviewers can approve or reject via:
- Dashboard / API — the account-scoped review queue
POST /v1/reviews/{id}/approveor/reject(id-addressed, no inbox email needed; lists held items across all the account's inboxes viaGET /v1/reviews). This is the only approve/reject path — a review'sidis the held message'sid. - MCP tools —
approve_review/reject_review(withlist_reviews/get_reviewto find them). - Magic-link email — sent automatically when a hold fires; one-click
GET /v1/approve?t=…and/v1/reject?t=…URLs (requiresE2A_PUBLIC_URLand outbound SMTP configured).
Enable review holds on an agent via PUT /v1/agents/{email}/protection: set the outbound gate action to review (or turn on the content scan), plus the hold TTL (holds.ttl_seconds) and its expiry behavior (holds.on_expiry = approve or reject). Posture lives entirely on the protection sub-resource.
API
[!IMPORTANT] The core
/v1API and SDKs are stable and generally available (GA) as of v1.5.0: no breaking changes within/v1. That tag is the compatibility baseline — every later release is audited against it. A small, explicitly enumerated surface is still beta and may change before it is declared stable — contacts & outreach, scheduled sending (send_at), email templates & starter templates, the reviews (HITL) queue, agent protection config, agent-scoped suppressions, managed unsubscribe, message lifecycle diagnostics, delivery metrics, and thethread_idmessage-read field. Beta surface is markedx-stability-level: betain the OpenAPI spec and(beta)in the docs; where only specific values of a stable field are beta (thescheduledsend status, the screening/review-hold event types, theblocked_by_policyerror code), the field carriesx-experimental-valuesnaming exactly those values. Everything else is covered by the GA freeze. See the full matrix in docs/api.md → Stability: GA and beta surface. Existingv1.0.xapplication/cherry-pick tags predate the API freeze and are not/v1compatibility baselines.
All endpoints are under /v1 unless noted. Auth is Authorization: Bearer <api_key> except for /api/health, /v1/info, /api/feedback, and the HITL magic-link routes. Path parameters containing @ (agent emails) must be URL-encoded.
The surface covers domain registration + verification, agent CRUD, inbound/outbound messages, webhook subscriptions, HITL approve/reject (API key or signed magic-link token), GDPR-style export and deletion, and a WebSocket channel for real-time inbound delivery.
See docs/api.md for the full endpoint reference, or api/openapi.yaml for the machine-readable spec.
MCP server
Shortened here. Read the whole README on GitHub.
Signals
- GitHub stars
- 185
- Forks
- 23
- Last commit
- Sep 2026
Advanced
- Delivery
- mcp-server MCP server → your ahel gateway (mcp.ahel.ai) → every connected AI client.
- Catalog kind
- mcp-server
- Gateway key
io-github-mnexa-ai-mcp-server- Source
- github.com/mnexa-ai/e2a
- Hosted endpoint
https://mcp.e2a.dev/mcp