Use Case

Giving a LangChain Agent an Email Inbox

TL;DR
Verdict An agent that cannot receive email cannot finish a signup. Three @tool functions close that gap, and putting the retry loop inside the tool keeps the model from burning tokens on polling.

Why the loop belongs in the tool

The obvious design gives the model a list_messages tool and lets it decide when to check again. It works, and it is wasteful. The model spends a full turn on every poll, reasoning about whether to wait, and a thirty second delivery can cost a dozen round trips.

Put the wait inside the tool instead. One call blocks until the message lands or the timeout expires. The model makes a single decision, and the polling happens in Python where it costs nothing.

Three tools

import os, re, time, requests
from langchain_core.tools import tool

API = "https://emptyinbox.me/api"
HEADERS = {"Authorization": f"Bearer {os.environ['EMPTYINBOX_API_KEY']}"}


@tool
def create_inbox() -> str:
    """Create a fresh disposable email address and return it."""
    r = requests.post(f"{API}/inbox", headers=HEADERS, timeout=10)
    r.raise_for_status()
    return r.text.strip()  # plain text, not JSON


@tool
def wait_for_message(address: str, timeout_seconds: int = 60) -> str:
    """Block until an email arrives at the address, then return its text body.

    Poll inside the tool rather than asking the model to retry: one call, one
    decision, no tokens spent on waiting.
    """
    deadline = time.monotonic() + timeout_seconds
    while time.monotonic() < deadline:
        r = requests.get(f"{API}/messages", headers=HEADERS, timeout=10)
        r.raise_for_status()
        for m in r.json():
            if m["inbox"] == address:
                return f"From: {m['sender']}\nSubject: {m['subject']}\n\n{m['text_body']}"
        time.sleep(3)
    return f"No email arrived at {address} within {timeout_seconds} seconds."


@tool
def list_inboxes() -> list:
    """List every inbox this account owns, with creation timestamps."""
    r = requests.get(f"{API}/inboxes", headers=HEADERS, timeout=10)
    r.raise_for_status()
    return r.json()

Bind them the usual way and the agent handles the rest.

from langchain.chat_models import init_chat_model
from langgraph.prebuilt import create_react_agent

model = init_chat_model("claude-sonnet-5", model_provider="anthropic")
agent = create_react_agent(model, [create_inbox, wait_for_message, list_inboxes])

result = agent.invoke({"messages": [
    ("user", "Sign up for the service at example.com with a throwaway address "
             "and confirm the verification code.")
]})

Let the model do the extraction

It is tempting to parse the code out with a regular expression and return only that. Resist it. Verification emails vary: six digits here, an eight character token there, sometimes only a link. A parser tuned to one service breaks silently on the next.

Returning the whole text body and letting the model find the code is more robust, and it is the thing language models are unambiguously good at. Keep the regular expression as a fallback in your own code if a downstream step needs the bare value.

The OpenAPI route

If you would rather not hand-write tools, the spec at /openapi.yaml describes every endpoint and loads into an OpenAPI toolkit directly. You get full coverage, including the payment endpoints, at the cost of a larger tool surface for the model to reason about.

For most agents the three hand-written tools are the better trade. Fewer tools, clearer names, and the blocking wait that the raw REST API does not provide.

Timeouts and cost

Sixty seconds is a reasonable default for the wait. Most verification mail arrives in a few seconds, and anything past a minute usually means the signup failed rather than that the mail is slow. Returning a clear message on timeout, as the tool above does, lets the agent report the failure instead of hanging.

Each create_inbox call consumes one credit. An agent that creates an inbox per task run through a few hundred tasks a month sits comfortably inside a $5 bundle.

Frequently asked questions

Should the agent poll for email itself?

No. Put the retry loop inside the tool so a single call blocks until the message arrives. Model-driven polling spends a turn per attempt and adds latency without adding reliability.

Can I load the API as an OpenAPI toolkit instead?

Yes. The spec at /openapi.yaml covers every endpoint. The trade is a larger tool surface and no blocking wait, since that behaviour lives in your wrapper rather than in the REST API.

Should the tool extract the verification code?

Usually not. Return the full text body and let the model find the code. Formats vary between services, and a regular expression tuned to one of them fails quietly on the rest.

Does this work with CrewAI or a custom agent loop?

Yes. The tools are ordinary Python functions wrapping HTTP calls, so any framework that accepts callables works the same way.

Give your agent an inbox

Free tier includes 5 inboxes. No credit card required.

Get API Key