
This is the SDK guide for AgentMail, an email API built specifically for AI agents that need real inboxes, not just transactional sending. You get instant two-way email addresses, thread management, and reply extraction that automatically strips quoted text so your agent only sees the new content. The standout feature is the agent sign-up flow that lets you create accounts and get API keys entirely from code, no console required. It covers Python and TypeScript SDKs with examples for sending, receiving, drafts for human-in-the-loop approval, WebSocket notifications, and multi-tenant pods. If you're building agents that need to handle email conversations programmatically, this gives you the full workflow without dealing with IMAP parsing or Gmail OAuth headaches.
npx -y skills add agentmail-to/agentmail-skills --skill agentmail-sdk --agent claude-codeInstalls into .claude/skills of the current project.
AgentMail is an API-first email platform for AI agents. Use the published SDK interfaces and generated API types as the source of truth. Keep credentials in AGENTMAIL_API_KEY.
npm install agentmail
pip install agentmail
Create an inbox, send, and read a reply. Full per-language usage lives in the references.
import { AgentMailClient } from "agentmail";
const client = new AgentMailClient({ apiKey: process.env.AGENTMAIL_API_KEY });
const inbox = await client.inboxes.create({ username: "support", clientId: "support-v1" });
await client.inboxes.messages.send(inbox.inboxId, {
to: ["customer@example.com"],
subject: "Hello",
text: "Plain-text body",
});
// .list() returns metadata only — fetch the full message to read the body.
const messages = await client.inboxes.messages.list(inbox.inboxId, { limit: 20 });
const message = await client.inboxes.messages.get(inbox.inboxId, "msg_123");
const body = message.extractedText ?? message.text ?? message.extractedHtml ?? message.html;
from agentmail import AgentMail
from agentmail.inboxes.types import CreateInboxRequest
client = AgentMail() # Reads AGENTMAIL_API_KEY.
inbox = client.inboxes.create(request=CreateInboxRequest(username="support", client_id="support-v1"))
client.inboxes.messages.send(
inbox_id=inbox.inbox_id,
to="customer@example.com",
subject="Hello",
text="Plain-text body",
)
messages = client.inboxes.messages.list(inbox_id=inbox.inbox_id, limit=20)
message = client.inboxes.messages.get(inbox_id=inbox.inbox_id, message_id="msg_123")
body = message.extracted_text or message.text or message.extracted_html or message.html
get(inboxId) and send(inboxId, request).CreateInboxRequest for configured organization-level inbox creation in Python.extracted_text / extracted_html, not text / html — they strip quoted history and signatures. Some clients (Gmail, Outlook) send forwards as HTML-only, so treat html as the primary fallback and text as optional.next_page_token or nextPageToken until the requested result range is complete.client_id or clientId for idempotent create operations.Traps that don't match intuition — read these before writing code, not after it fails.
messages.delete. Neither SDK supports deleting an individual message. To remove a conversation, delete the whole thread.reply() has no subject parameter. The parent subject is auto-reused (Re:-prefixed). To change subject, send a new message instead.webhooks.update is add/remove-only. It can only add or remove inbox_ids / pod_ids; it cannot change url or event_types — delete and recreate instead.threads.list has no pod_id filter. To scope to one pod, use client.pods.threads.list(pod_id).(direction, type, entry) per call; change = delete then recreate. See admin.md..query, not .get.max_retries is constructor-level in TypeScript only. Python overrides per call via request_options; TypeScript accepts maxRetries in the constructor.inboxes.create takes a request object, not flat kwargs — but client.pods.inboxes.create does take flat kwargs.get_attachment returns a signed URL, not bytes. The URL expires in ~1 hour and points at cdn.agentmail.to — fetch immediately, never persist the URL. See python.md / typescript.md.message.received.spam and message.received.blocked are accepted by the API but absent from the SDK's typed Literal; type checkers flag them as plain strings — expected, not a bug.Create an account and API key from code, no console needed. Requires agentmail>=0.4.15 in Python.
client = AgentMail() # no api_key needed for sign-up
response = client.agent.sign_up(human_email="you@example.com", username="my-agent")
# response.api_key, response.inbox_id, response.organization_id
client = AgentMail(api_key=response.api_key)
client.agent.verify(otp_code="123456")
const client = new AgentMailClient();
const response = await client.agent.signUp({ humanEmail: "you@example.com", username: "my-agent" });
// response.apiKey, response.inboxId, response.organizationId
const authed = new AgentMailClient({ apiKey: response.apiKey });
await authed.agent.verify({ otpCode: "123456" });
Warning: calling sign_up / signUp again with the same human_email ROTATES the API key — the old key stops working immediately. This is destructive, not idempotent: never call it just to "check" or "re-fetch" a key, and never treat repeated calls as safe.
For scoped API keys, permissions, and metrics, consult the current AgentMail API reference as the source of truth for exact signatures.