LangChain
An email inbox for your LangChain agent.
pip install langchain-pidgeon, and the agent gets an address of its own, reads the code from the next email, and asks a person before it sends anything. It runs with a real model against our production after every deploy.
from langchain.agents import create_agent
from langchain.agents.middleware import (
HumanInTheLoopMiddleware,
)
from langgraph.checkpoint.memory import (
InMemorySaver,
)
from langchain_pidgeon import PidgeonToolkit
tools = PidgeonToolkit().get_tools()
asks_first = {
tool.name: True
for tool in tools
if not tool.metadata["readOnlyHint"]
}
gate = HumanInTheLoopMiddleware(
interrupt_on=asks_first,
)
agent = create_agent(
"openai:gpt-5-mini",
tools=tools,
middleware=[gate],
checkpointer=InMemorySaver(),
)What it does
An address, a wait, and a person.
The toolkit is nine of the hosted MCP server’s tools over the Python client, with the same names and descriptions.
- An inbox it can wait on
The agent creates an address of its own, and await_message holds one request open for up to 280 seconds and returns the next email with its codes and links already pulled out.
- A person before anything is sent
Every tool that changes something carries readOnlyHint false, so LangChain’s HumanInTheLoopMiddleware pauses on it. The agent proposes the reply; a person approves or rejects it.
- The key is the boundary
The toolkit reads PIDGEON_API_KEY. Give the agent a key scoped to its own address, and it cannot read or send as anyone else, whatever the model decides.
Approve each send
The run pauses; a person answers.
Each paused call carries its name and arguments, so a person sees exactly what would be sent before it is.
The whole program, with an address, a code and a reply, is in the LangChain guide. It runs on a test key, so nothing it sends leaves Pidgeon.
first = {"messages": [{"role": "user", "content": task}]}
result = agent.invoke(first, config, version="v2")
# A tool that changes something pauses the run
# until a person answers.
while result.interrupts:
pending = result.interrupts[0]
decisions = []
for call in pending.value["action_requests"]:
if approve(call):
decisions.append({"type": "approve"})
else:
decisions.append(
{"type": "reject", "message": "The person said no."}
)
resume = Command(resume={"decisions": decisions})
result = agent.invoke(resume, config, version="v2")Questions
Asked before adding it.
Which tools does langchain-pidgeon have?
Nine: list_addresses, create_address, list_messages, search_messages, get_message, await_message, send_message, reply_to_message and update_thread. They are the hosted MCP server’s tools by name; for all 28, connect the server with langchain-mcp-adapters.
Does it work with LangGraph?
The agent above is LangChain’s create_agent, which runs on LangGraph; the pause and resume are LangGraph interrupts and Command. That is the loop we run with a real model after every deploy, on langchain 1.4.7 and langgraph 1.2.17.
Does new email wake the agent?
No. The agent has to be running and call await_message, which waits up to 280 seconds for the next message. To start a workflow when mail arrives, use a webhook, or the n8n node’s trigger.
Is there a JavaScript version?
No: langchain-pidgeon is Python. In TypeScript, @pidgeon-ai/ai-sdk gives a Vercel AI SDK agent the same tools, and any MCP client can use the hosted server.
Is it on the free plan?
Yes. The package is free and open source, and the free plan includes three addresses and your own domain.
Give your agent an address.
Paste one prompt, or make the call yourself. It is free to start.
Read https://pidgeon.ai/skill.md and follow the instructions to get your own inbox.
The full API reference is at https://pidgeon.ai/openapi.json