Toolkits
CarlyEmail's tools as native function tools for the OpenAI Agents SDK, LangChain, LiveKit Agents and the Vercel AI SDK.
The hosted MCP server is the shortest way to give an agent email. The toolkits are the same tools without MCP in the loop: plain function tools for your framework, calling the REST API through the SDK. Use one when your runtime does not speak MCP, when you want the tool list fixed at build time, or when you would rather not hold an MCP session open.
Install
pip
pip install "carlyemail-toolkit[openai]" # or [langchain], [livekit]
npm
npm install carlyemail-toolkit
Both read CARLYEMAIL_API_KEY, or take api_key= / { apiKey }, or an
existing CarlyEmail client. No inbox yet? npx carlyemail signup makes one.
Use it
OpenAI Agents SDK
from agents import Agent
from carlyemail_toolkit.openai import CarlyEmailToolkit
inbox_agent = Agent(
name="Inbox",
instructions="Read the thread before acting. Draft rather than send when unsure.",
tools=CarlyEmailToolkit().get_tools(),
)
LangChain
from langchain.agents import create_agent
from carlyemail_toolkit.langchain import CarlyEmailToolkit
agent = create_agent("openai:gpt-5-mini", CarlyEmailToolkit().get_tools())
LiveKit Agents
from livekit.agents import Agent
from carlyemail_toolkit.livekit import CarlyEmailToolkit
agent = Agent(instructions="...", tools=CarlyEmailToolkit().get_tools())
Vercel AI SDK
import { openai } from "@ai-sdk/openai";
import { ToolLoopAgent } from "ai";
import { CarlyEmailToolkit } from "carlyemail-toolkit/ai-sdk";
const agent = new ToolLoopAgent({
model: openai("gpt-5-mini"),
instructions: "Read the thread before acting. Draft rather than send when unsure.",
tools: new CarlyEmailToolkit().getTools(),
});
LangChain.js
import { createAgent } from "langchain";
import { CarlyEmailToolkit } from "carlyemail-toolkit/langchain";
const agent = createAgent({
model: "openai:gpt-5-mini",
tools: new CarlyEmailToolkit().getTools(),
});
For a framework not listed, the bare toolkit returns each tool's name,
description, JSON Schema, MCP-style annotations, and a function to call:
Python
from carlyemail_toolkit import CarlyEmailToolkit
for tool in CarlyEmailToolkit().get_tools():
print(tool.name, tool.read_only, tool.input_schema["required"])
TypeScript
import { CarlyEmailToolkit } from "carlyemail-toolkit";
for (const tool of new CarlyEmailToolkit().getTools()) {
console.log(tool.name, tool.annotations.readOnlyHint, tool.inputSchema.required);
}
What is in it
The 25 tools the hosted server serves, with the same names, descriptions and parameter schemas. They are written out of the server's own registry, so a toolkit install and an MCP connection describe each tool in the same words.
| Tools | |
|---|---|
| Inboxes | list_inboxes get_inbox create_inbox update_inbox delete_inbox |
| Threads | list_threads search_threads get_thread update_thread delete_thread |
| Messages | list_messages search_messages send_message reply_to_message forward_message update_message get_attachment |
| Drafts | create_draft list_drafts get_draft update_draft send_draft delete_draft |
| Account | auth_me verify_account |
Not included: search and fetch, which exist for ChatGPT's connector
contract, and request_verification_code, which has no REST route — the code
is sent at sign-up and verify_account redeems it.
Only some tools
Pass names. The order you name is the order you get, and a name that is not a tool is an error rather than an omission.
Python
tools = CarlyEmailToolkit().get_tools(["list_messages", "get_thread", "reply_to_message"])
TypeScript
const tools = new CarlyEmailToolkit().getTools(["list_messages", "get_thread", "reply_to_message"]);
That narrows what the model sees. The boundary that holds is an
inbox-scoped API key with only the permissions the agent
needs — a send through a key without message_send returns 403 whatever
the model decides.
Which inbox a call is about
Every tool takes inbox_id, and most work without it:
- A key scoped to one inbox means that inbox.
- An organization with one inbox means that one.
- With several, the tools that read threads and drafts look across all of them, and each result names the inbox it came from. Every other tool refuses and names the inboxes, so the agent can ask the person which one rather than choose for them.
- With none, reads come back empty with a
next_stepthat tells the agent to ask what the address should be and callcreate_inbox.
This is what the hosted server does, in the same words. An agent behaves the same whichever way it is connected.
Errors
A failed call reaches the model as one bounded line carrying the API's message
and the fix it suggests — an Error: … tool result on the OpenAI Agents SDK, a
handled ToolException on LangChain, a ToolError on LiveKit, a thrown Error
the AI SDK reports as a tool-error part — so the agent can act on it next
turn:
This key cannot send. Create a key with message_send. See https://docs.carlyemail.com/authentication (insufficient_permissions, HTTP 403)
Receiving mail
The toolkits are for acting. For being woken when mail arrives, register a
webhook and use the receiver in the SDK —
carlyemail.inbound.create_email_router in Python, carlyemail/webhooks in
TypeScript — which verifies the signature and filters what reaches your agent.
Make your agent reply to email walks through the
whole loop.