CarlyEmail docs

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_step that tells the agent to ask what the address should be and call create_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.

See also