Volver al blog
·17 min de lectura

How to Give AI Agents Their Own Email Inbox Using an MCP Server

A technical deep-dive into giving AI agents real, programmable email inboxes via MCP — covering SMTP ingestion, Redis pub/sub, five MCP tools, and security considerations for autonomous agent workflows.

MCP AI Agents Python Email Architecture FastAPI

1. Introduction

AI agents are increasingly capable of browsing the web, calling APIs, writing code, and coordinating complex multi-step workflows. But there is a persistent gap in most agent architectures: agents cannot receive email.

This turns out to matter more than it sounds. A significant fraction of real-world automation pipelines depends on email as a coordination primitive. Email is how services send verification codes. It is how systems deliver receipts, confirmations, alerts, and approval requests. It is how humans hand off asynchronous tasks to other humans — and, increasingly, to automated systems. Without a working inbox, an agent navigating a real-world workflow hits a wall the moment any external system tries to reach it.

Consider what happens when an agent is asked to create an account on a third-party service, confirm a subscription, or collect structured data delivered as an attachment. In each case, the agent needs an email address it actually controls — not a fake one, not a forwarding alias with a human behind it, but a live inbox it can query programmatically: create, poll for new arrivals, parse content, and react accordingly.

This article is a technical deep-dive into exactly how to build that capability using the Model Context Protocol (MCP). You will see how an MCP server turns email infrastructure — SMTP ingestion, async REST API, Redis pub/sub, real-time WebSocket delivery — into injectable agent tools that any MCP-compatible agent runtime can call without additional glue code.

The implementation covered here is the uncorreotemporal.com stack, a programmable disposable-email system with a native MCP server. By the end of this article you should understand every layer of the architecture, the rationale for each design decision, how to connect any MCP-compatible agent to a live inbox, and what security boundaries to enforce when you do.


2. What Is an MCP Server?

Model Context Protocol (MCP) is an open standard developed to give AI models a canonical interface for connecting to external tools, data sources, and services. Rather than every LLM provider inventing their own function-calling schema, MCP defines a common protocol layer: a server exposes a set of named tools with typed schemas, and any MCP-compatible client (Claude Desktop, Cursor, agent frameworks like LangGraph or AutoGen) can discover and invoke those tools at runtime without bespoke adapter code.

The analogy that has gained traction in the engineering community is USB-C for AI: one connector standard that works across heterogeneous devices. MCP has been described by The Verge as "a universal connectivity layer for AI," and the uptake in the agent ecosystem supports that framing. MCP servers now exist for file systems, databases, GitHub, Slack, browser automation, and many other domains. Email is a natural fit.

At the protocol level, an MCP server:

  • Exposes a tools/list endpoint that returns schema-annotated tool definitions.
  • Accepts tools/call requests from the agent runtime with named parameters.
  • Returns structured JSON responses the agent uses to continue its reasoning.

The transport is typically stdio (for local subprocess integration) or HTTP with Server-Sent Events (for networked integration). The agent never needs to know how the tool is implemented — it just receives a schema and a result.

In the email domain, the most prominent existing integration point is Gmail via MCP. Projects like neoforge's email-mcp server (built on the Gmail API) have demonstrated that email can be a first-class MCP tool surface. The approach here is different: rather than wrapping an existing mailbox, the infrastructure creates purpose-built, ephemeral inboxes on demand — inboxes that belong to the agent, that expire on a deterministic schedule, and that receive real SMTP traffic.


3. Why MCP Instead of a Direct REST API?

The straightforward alternative to an MCP server is to give the agent a set of REST API calls and let it invoke them via an HTTP tool. This works, but it has compounding costs that matter at scale.

Glue code per agent runtime. Every agent framework handles HTTP differently. LangChain tools, AutoGen functions, OpenAI function-calling schemas, LlamaIndex query engines — each requires a separate adapter. An MCP server provides one adapter that every MCP client already knows how to use.

Schema negotiation at runtime. With raw REST, the agent must receive the OpenAPI spec or have it hard-coded. With MCP, the agent calls tools/list and gets typed schemas automatically. Tool discovery is part of the protocol.

Authentication encapsulation. The MCP server holds the API key. The agent runtime never sees it. A direct REST integration typically requires the agent to manage credentials in its own context, increasing exposure surface.

Composability without prompt engineering. When email capabilities are registered as MCP tools, the agent can invoke them the same way it invokes any other tool — no special prompt templates, no chain-of-thought scaffolding to remember API shapes. The model reasons about tools by their descriptions; MCP tool descriptions carry that semantic signal.

Error contracts. MCP tool calls return structured errors the runtime can handle uniformly. A raw HTTP 422 requires the agent to parse the response body and reason about what went wrong.

The result is that an MCP email server drops into any compatible agent stack as a capability, not a dependency to manage.


4. Architecture for Giving Email Inboxes to AI Agents

The full system has five distinct layers. Each layer has a clear responsibility and a clean interface to the layer above it.

┌─────────────────────────────────────────────────────────────┐
│                     MCP CLIENT (Agent)                      │
│    Claude Desktop / Cursor / AutoGen / LangGraph / etc.     │
└───────────────────────────┬─────────────────────────────────┘
                            │ stdio or HTTP
┌───────────────────────────▼─────────────────────────────────┐
│                      MCP SERVER                             │
│   mcp/server.py  ·  5 tools  ·  UCT_API_KEY auth           │
└───────────────────────────┬─────────────────────────────────┘
                            │ HTTP + Bearer token
┌───────────────────────────▼─────────────────────────────────┐
│                  FASTAPI REST API                           │
│  /api/v1/mailboxes  ·  /api/v1/mailboxes/{addr}/messages   │
│  Async SQLAlchemy  ·  Pydantic v2  ·  Plan quota checks    │
└────────────┬──────────────────────────────────┬────────────┘
             │                                  │
┌────────────▼──────────┐          ┌────────────▼────────────┐
│  POSTGRESQL (asyncpg) │          │    REDIS (pub/sub)      │
│  mailboxes  messages  │          │  channel: mailbox:{addr}│
└────────────┬──────────┘          └────────────┬────────────┘
             │                                  │
┌────────────▼──────────────────────────────────▼────────────┐
│                  EMAIL DELIVERY PIPELINE                    │
│  deliver_raw_email()  ·  parse_email()  ·  quota checks    │
└────────────┬──────────────────────────────────┬────────────┘
             │                                  │
┌────────────▼──────────┐          ┌────────────▼────────────┐
│  SMTP INGESTION (dev) │          │   AWS SES + SNS (prod)  │
│  aiosmtpd port 25     │          │  POST /api/v1/ses/inbound│
└───────────────────────┘          └─────────────────────────┘

Mailbox ownership model

Every mailbox has an owner_type enum with three values:

  • anonymous — created without credentials; controlled by a session token in the response.
  • api — created with a Bearer API key; owned by the user account that key belongs to.
  • mcp — created via the MCP server; structurally identical to api but tagged for audit purposes.

MCP-created mailboxes participate in the same plan-based quota system as API-created ones. The plan model enforces max_mailboxes, max_messages_per_mailbox, max_ttl_minutes, and a boolean mcp_access flag. Agents operating on a plan without mcp_access=True are rejected at the MCP server layer before any REST call is made.

Inbox expiration

Expiration is deterministic and database-driven. Every mailbox stores an expires_at timestamp. A background task runs every 60 seconds and sets is_active=False on all rows where expires_at <= now(). No cron job, no external scheduler — the FastAPI lifespan manager starts and stops the task. This means expiration is precise to the polling interval and does not require distributed coordination.

Real-time delivery

When an email arrives, deliver_raw_email() inserts a Message row and immediately publishes to a Redis channel keyed mailbox:{address}. Any WebSocket subscriber on that channel receives {"event": "new_message", "message_id": "<uuid>"} within milliseconds. This is orthogonal to MCP — agents using the MCP tools poll via get_messages, while browser clients use the WebSocket stream.


5. How AI Agents Create, Read, and Delete Inboxes via MCP

The MCP server at mcp/server.py exposes five tools. Here is each tool's signature, a representative call, and its response shape.

Tool 1: create_mailbox

Creates a new inbox with an optional TTL. The agent receives a real, live email address.

# Tool schema
name: "create_mailbox"
description: "Create a new temporary email inbox. Returns the address and expiry time."
parameters:
  ttl_minutes: integer (optional, default from plan)

Agent call:

{
  "name": "create_mailbox",
  "arguments": { "ttl_minutes": 30 }
}

Response:

{
  "address": "mango-panda-42@uncorreotemporal.com",
  "expires_at": "2026-03-03T15:30:00Z",
  "ttl_minutes": 30
}

The address is immediately live. Any SMTP sender can deliver to it. The friendly address format (adjective-noun-number@domain) is human-readable so agents can use it in places where a human might eventually see it — form fields, API registration flows, log outputs.

Tool 2: list_mailboxes

Returns all active inboxes associated with the API key used to authenticate the MCP server.

{
  "name": "list_mailboxes",
  "arguments": {}
}

Response:

{
  "mailboxes": [
    {
      "address": "mango-panda-42@uncorreotemporal.com",
      "expires_at": "2026-03-03T15:30:00Z",
      "message_count": 3
    },
    {
      "address": "coral-tiger-17@uncorreotemporal.com",
      "expires_at": "2026-03-03T16:00:00Z",
      "message_count": 0
    }
  ]
}

This allows an agent to audit its own inbox state and decide whether to create a new one or reuse an existing one.

Tool 3: get_messages

Lists message metadata for a given inbox. Intentionally does not return body content — that requires an explicit read_message call, keeping response payloads small.

{
  "name": "get_messages",
  "arguments": {
    "address": "mango-panda-42@uncorreotemporal.com",
    "limit": 10
  }
}

Response:

{
  "messages": [
    {
      "id": "3f8a1c2b-...",
      "from_address": "noreply@someservice.com",
      "subject": "Please confirm your email address",
      "received_at": "2026-03-03T14:47:31Z",
      "is_read": false,
      "has_attachments": false
    }
  ]
}

Tool 4: read_message

Fetches the full message including plaintext body, HTML body, and attachment metadata. Calling this tool marks the message is_read=True — consistent with how a human email client behaves.

{
  "name": "read_message",
  "arguments": {
    "address": "mango-panda-42@uncorreotemporal.com",
    "message_id": "3f8a1c2b-..."
  }
}

Response:

{
  "id": "3f8a1c2b-...",
  "from_address": "noreply@someservice.com",
  "to_address": "mango-panda-42@uncorreotemporal.com",
  "subject": "Please confirm your email address",
  "body_text": "Click here to confirm: https://someservice.com/confirm?token=abc123",
  "body_html": "<p>Click <a href=\"https://someservice.com/confirm?token=abc123\">here</a>...</p>",
  "attachments": [],
  "received_at": "2026-03-03T14:47:31Z"
}

The parser extracts body_text and body_html separately from the raw RFC 2822 message. Attachments are returned as metadata only (filename, content_type, size) — binary content is never included in the MCP response.

Tool 5: delete_mailbox

Soft-deletes the inbox. Immediately stops accepting new messages. Existing messages remain in the database for the retention period.

{
  "name": "delete_mailbox",
  "arguments": { "address": "mango-panda-42@uncorreotemporal.com" }
}

Response:

{ "success": true, "address": "mango-panda-42@uncorreotemporal.com" }

6. Real Use Cases with MCP Email

Use case 1: Automated service registration with email verification

Many services require email verification before granting API access or enabling downloads. An agent can complete this flow end-to-end.

# Pseudocode: Agent registers for a service that requires email verification

inbox = mcp.call("create_mailbox", ttl_minutes=15)
address = inbox["address"]

# Agent navigates to service registration page
browser.fill("#email", address)
browser.click("#submit")

# Agent polls for the verification email
for attempt in range(10):
    time.sleep(5)
    messages = mcp.call("get_messages", address=address, limit=5)

    verification_emails = [
        m for m in messages["messages"]
        if "confirm" in m["subject"].lower() and not m["is_read"]
    ]

    if verification_emails:
        msg = mcp.call("read_message",
                       address=address,
                       message_id=verification_emails[0]["id"])

        # Extract verification link from body_text
        token = extract_url(msg["body_text"], pattern=r"confirm\?token=(\w+)")
        browser.navigate(f"https://someservice.com/confirm?token={token}")
        break

mcp.call("delete_mailbox", address=address)

This pattern works for any service using link-based email verification — the agent creates the inbox, uses the address during registration, waits for delivery, extracts the token, and cleans up.

Use case 2: Receiving structured data from external systems

An agent coordinating with a legacy system that delivers reports via email can create a dedicated inbox and process incoming data:

# Agent sets up a dedicated reporting inbox
inbox = mcp.call("create_mailbox", ttl_minutes=1440)  # 24 hours
report_address = inbox["address"]

# Register address with the external system via its API
external_api.set_report_destination(report_address)

# Later: collect and process delivered reports
messages = mcp.call("get_messages", address=report_address, limit=50)

for msg_meta in messages["messages"]:
    full_msg = mcp.call("read_message",
                        address=report_address,
                        message_id=msg_meta["id"])

    # Parse CSV/JSON data from body_text or check attachments
    process_report(full_msg["body_text"])

Use case 3: Human-in-the-loop approval via email

An agent running a long-horizon task can send a status update to a human supervisor using an outbound SMTP relay and create a reply-to inbox to receive the approval signal:

inbox = mcp.call("create_mailbox", ttl_minutes=60)
reply_address = inbox["address"]

# Agent composes and sends a status email to the human
smtp_client.sendmail(
    from_addr=f"agent@yourdomain.com",
    to_addr="supervisor@company.com",
    msg=build_approval_request(reply_to=reply_address, task_summary=summary)
)

# Agent waits for reply
for _ in range(720):  # Poll for up to 60 minutes
    time.sleep(5)
    messages = mcp.call("get_messages", address=reply_address, limit=5)
    unread = [m for m in messages["messages"] if not m["is_read"]]

    if unread:
        reply = mcp.call("read_message",
                         address=reply_address,
                         message_id=unread[0]["id"])

        if "approved" in reply["body_text"].lower():
            proceed_with_task()
        else:
            abort_task(reason=reply["body_text"])
        break

Use case 4: Multi-agent inbox assignment

In a multi-agent system, each agent in a pipeline can be assigned its own inbox:

# Orchestrator assigns inboxes to worker agents
worker_inboxes = {}
for worker_id in worker_ids:
    inbox = mcp.call("create_mailbox", ttl_minutes=120)
    worker_inboxes[worker_id] = inbox["address"]
    worker_registry.assign_inbox(worker_id, inbox["address"])

# Coordinator can check any worker's inbox
coordinator_check = mcp.call("get_messages",
                              address=worker_inboxes["worker-03"],
                              limit=10)

7. Integrating the MCP Email Server with AI Platforms

Claude Desktop

The stdio transport is the standard integration path for local MCP servers. Add the server to your Claude Desktop configuration:

// ~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "uncorreotemporal": {
      "command": "python",
      "args": ["-m", "mcp.server"],
      "env": {
        "UCT_API_KEY": "uct_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }
    }
  }
}

After restarting Claude Desktop, the five tools appear in the tool list. Claude can call create_mailbox, list_mailboxes, get_messages, read_message, and delete_mailbox directly from its reasoning loop.

Cursor

Cursor supports MCP servers via the same stdio transport. Add to .cursor/mcp.json in your project root:

{
  "mcpServers": {
    "uncorreotemporal": {
      "command": "python",
      "args": ["-m", "mcp.server"],
      "env": {
        "UCT_API_KEY": "uct_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }
    }
  }
}

AutoGen / LangGraph (programmatic agents)

For programmatic agent frameworks that support MCP clients, the server process is launched as a subprocess and the tools are registered automatically via the protocol:

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

server_params = StdioServerParameters(
    command="python",
    args=["-m", "mcp.server"],
    env={"UCT_API_KEY": "uct_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
)

async with stdio_client(server_params) as (read, write):
    async with ClientSession(read, write) as session:
        await session.initialize()

        # Tools are now available
        tools = await session.list_tools()

        result = await session.call_tool(
            "create_mailbox",
            arguments={"ttl_minutes": 20}
        )
        address = result.content[0].text  # JSON string

Direct REST API (fallback)

If an agent framework does not yet support MCP, the same functionality is available via the REST API using a Bearer token:

# Create inbox
curl -X POST https://uncorreotemporal.com/api/v1/mailboxes \
  -H "Authorization: Bearer uct_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"ttl_minutes": 30}'

# Get messages
curl https://uncorreotemporal.com/api/v1/mailboxes/mango-panda-42@uncorreotemporal.com/messages \
  -H "Authorization: Bearer uct_xxxxxxxx"

# Read specific message
curl https://uncorreotemporal.com/api/v1/mailboxes/mango-panda-42@uncorreotemporal.com/messages/3f8a1c2b-... \
  -H "Authorization: Bearer uct_xxxxxxxx"

8. Security Considerations

Exposing email infrastructure to AI agents introduces a distinct threat surface. Address each of the following before deploying agents with inbox access in production.

API key scope and rotation

The MCP server authenticates with a single API key (UCT_API_KEY). This key has the same privilege level as the user account that generated it — meaning it can access all mailboxes owned by that account.

Best practices:

  • Create a dedicated user account per agent or agent system.
  • Rotate API keys on a schedule. The system stores only the SHA-256 hash of the key, and the raw key is shown only once at creation time.
  • Do not share API keys across agent instances that have different trust levels.

Inbox isolation

Inboxes created by MCP are tagged owner_type=mcp and are only accessible via the API key that created them. No cross-owner access is possible at the API layer. However, the email address itself is the access token for SMTP delivery — any sender that knows the address can deliver to it.

Mitigations:

  • Use short TTLs (ttl_minutes=10 or ttl_minutes=30) for transient use cases. The address is useless once the inbox expires.
  • Use the friendly address format for readability but treat addresses as semi-private. Do not log them in publicly accessible systems.
  • Delete inboxes immediately after the agent's task completes rather than waiting for TTL expiration.

Content injection via email body

An adversarial sender could deliver an email whose body contains instructions intended to manipulate the agent's reasoning. This is a variant of prompt injection: the agent reads the email body, the body contains attacker-controlled text, and the agent might act on that text as though it were a legitimate instruction.

Mitigations:

  • Treat email body content as untrusted external data, not as agent instructions. Use a separate reasoning step to classify and validate content before acting on it.
  • Where possible, structure email processing around expected formats (tokens, URLs matching a known pattern, numeric codes) rather than free-form text.
  • Avoid passing raw email body text directly into a prompt where an LLM might interpret it as a system message or instruction.

Message quota and resource exhaustion

An agent that creates inboxes in a tight loop — due to a bug, a retry storm, or adversarial input — will exhaust the plan's max_mailboxes quota. The system enforces this at the API layer and returns an HTTP 429 (rate limit) or 403 (quota exceeded) before creating a new inbox.

Best practices:

  • Reuse existing inboxes (list_mailboxes before create_mailbox).
  • Implement exponential backoff in agent retry logic.
  • Monitor message_count values via list_mailboxes and alert when counts are unexpectedly high (could indicate email spam targeting agent inboxes).

Attachment handling

The read_message tool returns attachment metadata only (filename, content_type, size) — binary content is never included in the JSON response. This prevents agents from inadvertently receiving and acting on executable or malicious binary payloads via the MCP tool surface.

If your use case requires attachment content, retrieve it directly via the REST API with explicit user authorization, not via automated agent code.

Transport security

All REST API calls and MCP server communication should be over TLS. In production deployment on AWS, the Application Load Balancer terminates TLS at the edge. The redis_url for ElastiCache uses rediss:// (TLS) rather than redis://. Ensure the MCP server subprocess inherits network-level controls appropriate for your environment.


9. Final Thoughts

The missing primitive for many real-world AI agent workflows is not reasoning capability — it is the ability to participate in asynchronous communication channels that the rest of the world takes for granted. Email is the most universal of those channels. It underpins account registration, notification delivery, human approval workflows, and inter-system coordination across virtually every industry.

By pairing a real SMTP ingestion backend with an MCP server, it becomes possible to give agents email inboxes with the same ease that you give them web search or database access: a tool registration, an API key, and a schema the model already knows how to use. The inboxes are real — they receive actual SMTP traffic, they are addressable by any email client on the internet, and they expire cleanly on a deterministic schedule.

The MCP layer is what makes this composable rather than one-off. Instead of writing adapter code for each agent framework, you write a server once and every MCP-compatible runtime picks it up automatically. As the protocol matures and adoption across Claude, Cursor, and open-source agent frameworks deepens, MCP-native email becomes a capability any agent can pick up without infrastructure expertise.

For teams building autonomous systems today: if your agents need to interact with the real world — verify accounts, collect confirmations, route human approvals — a programmable email inbox via MCP is the straightest path from prototype to production. The five tools covered here (create_mailbox, list_mailboxes, get_messages, read_message, delete_mailbox) cover the majority of real agent email workflows without requiring the agent to manage SMTP connections, parse RFC 2822, or handle Redis pub/sub subscriptions.

Try the full stack at uncorreotemporal.com. API keys and MCP integration are available for registered users.


Architecture references: FastAPI async REST layer (Python 3.11+), aiosmtpd for SMTP ingestion, AWS SES + SNS for production delivery, PostgreSQL 15 with asyncpg, Redis 7 pub/sub, AWS infrastructure provisioned with Terraform 1.5+.

Written by

FP
Francisco Pérez Ferrer

Software Engineer · Sr. Python Developer · AWS Certified Solutions Architect

Software engineer with 20 years of experience building Python backends, cloud infrastructure, and AI agent tooling. Builder of UnCorreoTemporal.

LinkedIn

Ready to give your AI agents a real inbox?

Create your first temporary mailbox in 30 seconds. Free plan available.

Create your free mailbox