LangChain and LangGraph
Load CarlyEmail's hosted MCP tools, invoke a LangGraph agent from verified mail, and preserve one graph thread per email thread.
The simplest LangChain MCP integration uses CarlyEmail's hosted server. It supplies the live tool schemas, while a FastAPI webhook invokes the graph whenever a real email arrives.
1. Install and create an inbox
pip install carlyemail langchain langchain-openai langchain-mcp-adapters fastapi uvicorn
npx carlyemail signup --human-email you@example.com --username assistant
npx carlyemail verify 123456
export OPENAI_API_KEY=sk-...
export CARLYEMAIL_API_KEY=ce_us_...
export CARLYEMAIL_INBOX=assistant@carlyemail.com
export CARLYEMAIL_WEBHOOK_SECRET=whsec_...
2. Load CarlyEmail's tools
Python
import asyncio
import os
from langchain.agents import create_agent
from langchain_mcp_adapters.client import MultiServerMCPClient
async def main():
client = MultiServerMCPClient({
"carlyemail": {
"transport": "http",
"url": "https://api.carlyemail.com/mcp",
"headers": {
"Authorization": f"Bearer {os.environ['CARLYEMAIL_API_KEY']}"
},
}
})
tools = await client.get_tools()
agent = create_agent(
model="openai:gpt-5-mini",
tools=tools,
system_prompt=(
f"You manage {os.environ['CARLYEMAIL_INBOX']}. "
"Read the message and thread before acting. "
"Draft rather than send when intent or authority is ambiguous."
),
)
result = await agent.ainvoke({
"messages": [{"role": "user", "content": "Summarize new mail."}]
})
print(result["messages"][-1].content)
asyncio.run(main())
MCP removes the wrapper-maintenance step. Filter tools before passing them to
create_agent when the model needs only a smaller surface, and enforce the
allowed operations with an inbox-scoped API key.
3. Make incoming email invoke the graph
This complete FastAPI app keeps a graph checkpoint thread for each CarlyEmail
thread_id:
Python
import json
import os
from contextlib import asynccontextmanager
from carlyemail.inbound import create_email_router
from fastapi import FastAPI
from langchain.agents import create_agent
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.checkpoint.memory import InMemorySaver
@asynccontextmanager
async def lifespan(app: FastAPI):
client = MultiServerMCPClient({
"carlyemail": {
"transport": "http",
"url": "https://api.carlyemail.com/mcp",
"headers": {
"Authorization": f"Bearer {os.environ['CARLYEMAIL_API_KEY']}"
},
}
})
app.state.agent = create_agent(
model="openai:gpt-5-mini",
tools=await client.get_tools(),
checkpointer=InMemorySaver(),
system_prompt=(
"You manage an email inbox. Read the thread before acting. "
"Reply in the existing thread, and draft when unsure."
),
)
yield
app = FastAPI(lifespan=lifespan)
async def on_email(email):
await app.state.agent.ainvoke(
{
"messages": [{
"role": "user",
"content": (
"Handle this newly received email. Use CarlyEmail's reply "
"tool if a reply is appropriate.\n\n" + json.dumps(email.message)
),
}]
},
{"configurable": {"thread_id": email.thread_id}},
)
app.include_router(
create_email_router(
on_email,
path="/hooks/carlyemail",
allow_from=["you@example.com"],
)
)
create_email_router verifies the signature over the raw bytes, admits
message.received and nothing else, drops mail the inbox sent itself, checks
the sender, ignores redeliveries, and answers the request before the graph runs
— so a run that outlasts the delivery timeout is not retried into a second
reply. See receiving mail for the options and for doing it
without FastAPI.
InMemorySaver makes the example runnable. Replace it with a durable LangGraph
checkpointer in production, keeping CarlyEmail thread_id as the graph
thread_id.
Run and register the route:
uvicorn app:app --host 0.0.0.0 --port 8000
npx carlyemail webhook https://your-agent.example/hooks/carlyemail \
--events message.received
Without MCP
carlyemail-toolkit gives you the same tools as native LangChain tools, with
no MCP client in the loop, in Python and in JavaScript:
Python
from langchain.agents import create_agent
from carlyemail_toolkit.langchain import CarlyEmailToolkit
agent = create_agent("openai:gpt-5-mini", CarlyEmailToolkit().get_tools())
TypeScript
import { createAgent } from "langchain";
import { CarlyEmailToolkit } from "carlyemail-toolkit/langchain";
const agent = createAgent({ model: "openai:gpt-5-mini", tools: new CarlyEmailToolkit().getTools() });
See Toolkits for naming only some tools and for how a call finds its inbox.
langchain-carlyemail adds a message loader, a search retriever, and a verified
inbound router:
pip install "langchain-carlyemail[webhooks]"
Python
from langchain_carlyemail import CarlyEmailLoader, CarlyEmailRetriever
documents = CarlyEmailLoader("assistant@carlyemail.com", max_messages=200).load()
retriever = CarlyEmailRetriever.from_api_key("assistant@carlyemail.com", limit=10)
Its create_email_router wraps the same receiver shown above, and hands your
callback a CarlyEmailEvent rather than an InboundEmail.
Warning
Email bodies and attachments are untrusted model input, and a verified delivery
says CarlyEmail sent it — not that the sender's instructions are safe to follow.
The router handles the transport; the boundary that holds is an
inbox-scoped API key. Omit message_send when a person
should approve drafts before they leave.