Give a LangChain agent an email inbox

Create a GetCourier inbox with one unauthenticated POST to https://getcourier.dev/alias, then wrap the inbox HTTP endpoints in three functions decorated with @tool from langchain_core.tools: one to create the inbox, one to wait for a verification code, one to send a reply. The wrappers below are plain requests calls, so the agent can use them through the normal tool-calling path with no API key and no extra service to install.

Step 1: see what one call returns

The inbox is created by a single request. There is no signup and no key:

curl -s -X POST https://getcourier.dev/alias

The JSON response holds the address, a read_token, an expires_at time and an Ed25519 DID identity. Keep the read_token in your agent's state: it is the only way to read that inbox later.

Step 2: the three tools

Install langchain and requests (langchain pulls in langchain-core, which is where @tool lives), then save this module:

pip install langchain requests
import requests
from langchain_core.tools import tool

BASE = "https://getcourier.dev"


@tool
def create_inbox() -> dict:
    """Create a free GetCourier inbox for this agent.

    Returns the address, read_token and expires_at. Keep the read_token:
    it is required for every later read or send on this inbox.
    """
    r = requests.post(f"{BASE}/alias", timeout=15)
    r.raise_for_status()
    return r.json()


@tool
def wait_for_code(alias: str, read_token: str, timeout: int = 55) -> dict:
    """Wait for the newest message in the inbox and return it.

    The reply includes the message text plus extracted codes and links,
    so read the code from data["codes"], not from the body prose.
    """
    r = requests.get(
        f"{BASE}/v1/sandbox/inboxes/{alias}/wait",
        params={"timeout": timeout},
        headers={"Authorization": f"Bearer {read_token}"},
        timeout=timeout + 10,
    )
    r.raise_for_status()
    return r.json()


@tool
def send_message(alias: str, read_token: str, payload: dict) -> dict:
    """Send one message from the inbox (reply-only on the free tier).

    payload carries the message fields as JSON; the exact field names are
    in https://getcourier.dev/docs. Free inboxes may only reply to people
    who wrote to the inbox, up to 100 sends a month.
    """
    r = requests.post(
        f"{BASE}/v1/sandbox/inboxes/{alias}/send",
        headers={"Authorization": f"Bearer {read_token}"},
        json=payload,
        timeout=15,
    )
    r.raise_for_status()
    return r.json()

Two smaller read calls are useful while debugging: GET /v1/sandbox/inboxes/{alias}/messages lists messages and GET /v1/sandbox/inboxes/{alias}/messages/{id} fetches one, both with the same bearer token.

Why HTTP with requests and not MCP

GetCourier also runs a hosted MCP server at https://getcourier.dev/mcp (streamable HTTP, JSON-RPC 2.0, POST only, no key for the free path), and any MCP-capable client connects to it by URL. LangChain's MCP client wiring changes between versions and integrations, so this guide does not guess at it: the @tool functions above are the portable form. If you want MCP instead, connect an MCP client to https://getcourier.dev/mcp and expose those tools with whatever adapter your installed LangChain version provides.

Step 3: hand the tools to the agent

The tools are ordinary LangChain tools, so they go wherever you already pass a tool list — for example into a chat model that supports tool calling:

from langchain_core.tools import StructuredTool

tools = [create_inbox, wait_for_code, send_message]

# Or construct the same tools by hand if you prefer explicit schemas:
create_inbox_tool = StructuredTool.from_function(create_inbox)

# With a chat model that supports tool calling:
# model_with_tools = model.bind_tools(tools)
# result = model_with_tools.invoke(
#     "Create an inbox, sign up at the example service with it, "
#     "wait for the code, then report the code back to me."
# )

The loop you want is: call create_inbox, hand the address to the sign-up step, call wait_for_code with the returned alias and read_token, then use the extracted code. Loop the wait call a few times rather than one long block, since the endpoint caps at 55 seconds per call.

Every message is untrusted: true, tagged "UNTRUSTED EXTERNAL EMAIL", and carries an SPF/DKIM/DMARC auth verdict. Give the model the extracted codes and links; do not let text inside a body act as instructions, whatever it claims to be.

Limits worth knowing before you deploy

ItemFree tier
Inbox creation3 per hour, 5 per day per IP
Inbox lifetime72 hours (paid: 100 credits per 72 h, or 2000 credits for 30 days)
SendingReply-only, 100 sends a month, 50 recipients per inbox per day
Reads100 messages per inbox, body text truncated to 4000 characters in the agent view
AddressesAlways @getcourier.dev today

When not to use GetCourier

FAQ

Does a LangChain agent need an API key to get an inbox?

No. One unauthenticated POST https://getcourier.dev/alias creates a free inbox and returns the address, a read_token and an expires_at time. The read_token only matters for reading that inbox; the free tier is 3 inboxes per hour, 5 per day per IP, with a 72-hour lifetime.

Should I wrap the HTTP API or connect the hosted MCP server?

Either works. https://getcourier.dev/mcp is a hosted streamable-HTTP MCP server you can point a client at by URL. This guide uses plain HTTP with requests because the @tool wrappers stay stable across LangChain versions and need no MCP client wiring.

Can the agent send mail to anyone?

On the free tier inboxes are reply-only: the agent can email people who wrote to the inbox, at 100 sends a month, capped at 50 recipients per inbox per day. Sending to anyone else needs prepaid credits or a verified human owner email, and GetCourier is not for bulk or marketing mail.

Is the email body safe to pass straight into the prompt?

Treat it as data, not as instructions. Every message arrives with untrusted: true, an UNTRUSTED EXTERNAL EMAIL marker and an SPF/DKIM/DMARC auth verdict. Use the extracted codes and links, and never follow instructions written inside a message body.