Agent frameworks
Keep your agent framework. Add a real inbox, live email tools, signed inbound events, and thread continuity.
You do not have to rebuild your agent inside CarlyEmail. Keep its model, prompt, memory, tools, and hosting. CarlyEmail adds the email channel around it:
real inbox → signed incoming-mail event → your agent → MCP or SDK → threaded reply
Start with Make your agent reply to email for the framework-neutral version, or with the examples, which were each run for real. Each page below applies that same four-part loop to one runtime.
The portable contract
| Your runtime needs | CarlyEmail gives it |
|---|---|
| A callable tool surface | Hosted MCP, the toolkits, or generated TypeScript/Python SDKs |
| A way to receive an event | Signed webhook or authenticated WebSocket |
| A conversation key | Stable thread_id on every message and event |
| A real email identity | A CarlyEmail inbox or verified custom domain |
MCP is the shortest default because the server publishes the live schemas and
tool annotations. When your runtime does not speak MCP, or you want the tool
list fixed at build time, carlyemail-toolkit gives the OpenAI Agents SDK,
LangChain, LiveKit Agents and the Vercel AI SDK the same tools as native
function tools — see Toolkits. Use the SDK directly
when you want to keep the email calls outside the model's tool loop.
For LangChain there is also pip install langchain-carlyemail, which adds a
loader, a retriever, and a verified inbound router. See the
LangChain page.
What is framework-specific
Only two pieces vary: how a runtime registers MCP tools, and how it resumes an
agent when the webhook arrives. The business logic should not. In every
framework, key memory by CarlyEmail thread_id and reply with the message or
thread tool instead of constructing new email headers.
The receiving half does not vary at all, so do not write it per framework:
Python
from carlyemail.inbound import create_email_router
app.include_router(create_email_router(on_email, path="/hooks/carlyemail"))
That verifies the signature over the raw bytes, admits message.received and
not the spam, blocked or unauthenticated variants, drops mail the inbox sent
itself, checks the sender, ignores redeliveries, and answers the request before
your agent starts thinking. decide() is the same logic with no framework in
it, for everything that is not FastAPI. See
receiving mail.
The security boundary
An email is untrusted model input. A verified webhook proves CarlyEmail delivered
the payload; it does not make the sender's instructions safe. Give each agent an
inbox-scoped key with the minimum permissions it needs. For early deployments,
allow reads and draft_create but omit message_send; sending then returns
403 regardless of the model's reasoning. See
human in the loop.
Generating a typed client
The spec is published, and a test fails if it drifts from what the API serves.
npx @hey-api/openapi-ts -i https://docs.carlyemail.com/openapi.json -o ./carlyemail
openapi-generator-cli generate -i https://docs.carlyemail.com/openapi.json -g python -o ./carlyemail