CAT
/Skills
SkillsMCPMarketplacesDigestToolsAdvertise

This week in Claude

Every Monday: Claude Code, Agent SDK, MCP, and the Anthropic platform moves worth your time.

Skills by Category
Frontend DevelopmentBackend & APIsTesting & QASecurityDevOps & CI/CDGit & Pull RequestsDocumentationCode Review & QualityAI & Agent BuildingSkill Development
MCP Servers by Category
Sales & MarketingWeb & Browser AutomationDatabasesAI & LLM ToolsCloud & InfrastructureCommunication & MessagingDeveloper ToolsDesign & CreativeDocuments & KnowledgeSearch & Web Crawling
Marketplaces by Category
AI Agents & OrchestrationLLM IntegrationDevelopment ToolsFrontend & UIBackend & APIsDatabasesTesting & Code QualityDevOps & CloudSecurity & ComplianceGit & Version Control

Cross AI Tools

Discover Claude Code plugins, extensions, and tools. Automatically updated directory of Anthropic Claude AI marketplaces with development tools, productivity plugins, and integrations.

Resources

  • Browse Skills
  • Browse MCP Servers
  • Browse Marketplaces
  • Plugins Reference

Community

  • About
  • Tools
  • Feedback
  • Privacy Policy
  • Advertise

Built for the Claude Code community with Claude Code by @mertduzgun

Independent project, not affiliated with Anthropic

Agentmail Sdk

agentmail-to/agentmail-skills
183 installs15 stars
Summary

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.

Install to Claude Code

npx -y skills add agentmail-to/agentmail-skills --skill agentmail-sdk --agent claude-code

Installs into .claude/skills of the current project.

CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
Keep your Mac awake
Keep your Mac awake
Keep your Mac awake while Claude Code and 40+ AI agents run. Sleeps when they're idle.
One time payment $9 →
Context.devContext.dev
Context.dev
Integrate web data into your AI product. One API to scrape website & brand data.
Get API Key Now →
Make your agent a DeFi expert
Make your agent a DeFi expert
Agent, run crypto. Access onchain data & trade routes via 1inch.
Install now →
Make money from your Skills
Make money from your Skills
On Capafy, your Skill runs online 24/7 as an agent product, and you get paid every time someone uses it.
Start earning →
AppSignal
AppSignal
Monitor with ease. Code with confidence.
Start Free Trial →
CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
Keep your Mac awake
Keep your Mac awake
Keep your Mac awake while Claude Code and 40+ AI agents run. Sleeps when they're idle.
One time payment $9 →
Context.devContext.dev
Context.dev
Integrate web data into your AI product. One API to scrape website & brand data.
Get API Key Now →
Make your agent a DeFi expert
Make your agent a DeFi expert
Agent, run crypto. Access onchain data & trade routes via 1inch.
Install now →
Make money from your Skills
Make money from your Skills
On Capafy, your Skill runs online 24/7 as an agent product, and you get paid every time someone uses it.
Start earning →
AppSignal
AppSignal
Monitor with ease. Code with confidence.
Start Free Trial →
Files
SKILL.mdView on GitHub

AgentMail SDK

AgentMail is an API-first email platform built for AI agents. Unlike transactional email APIs (Resend, SendGrid) that focus on one-way sending, AgentMail provides full two-way email inboxes that agents can create, send from, receive into, and manage programmatically.

Key capabilities:

  • Instant inbox creation (milliseconds, no domain setup needed)
  • Two-way conversations with native thread management
  • Reply extraction (extracted_text) strips quoted history automatically
  • WebSocket and webhook support for real-time inbound
  • Human-in-the-loop drafts for agent oversight
  • Multi-tenant isolation with pods
  • Allow/block lists for sender filtering
  • IMAP and SMTP access for legacy integrations

Installation and setup

# Python
pip install agentmail

# TypeScript / Node.js
npm install agentmail

Get your API key from https://console.agentmail.to/ or via the Agent sign-up API (see below).

Python:

from agentmail import AgentMail
client = AgentMail(api_key="YOUR_API_KEY")
# Or set AGENTMAIL_API_KEY env var and omit api_key:
# client = AgentMail()

TypeScript:

import { AgentMailClient } from "agentmail";
const client = new AgentMailClient({ apiKey: "YOUR_API_KEY" });

Agent sign-up (programmatic, no console needed)

Create an account and get an API key entirely from code. No browser required.

Requires agentmail>=0.4.15 (Python) / agentmail>=0.x (TypeScript). If your installed SDK raises AttributeError: 'AgentMail' object has no attribute 'agent', upgrade first.

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   -> store this securely
# response.inbox_id  -> my-agent@agentmail.to
# response.organization_id

# Verify with OTP sent to your email
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 authedClient = new AgentMailClient({ apiKey: response.apiKey });
await authedClient.agent.verify({ otpCode: "123456" });

The sign-up endpoint is idempotent: calling again with the same email rotates the API key and resends the OTP.

Inboxes

Create scalable inboxes on-demand. Each inbox has a unique email address. No domain verification needed for @agentmail.to.

from agentmail.inboxes.types import CreateInboxRequest

# Create inbox (auto-generated address)
inbox = client.inboxes.create()
# inbox.inbox_id, inbox.email

# Create with options. All create kwargs go inside a CreateInboxRequest.
inbox = client.inboxes.create(
    request=CreateInboxRequest(
        username="support",
        domain="yourdomain.com",       # optional, defaults to agentmail.to
        display_name="Support Agent",
        client_id="support-v1",        # idempotency key, safe to retry
    ),
)

# List all inboxes
inboxes = client.inboxes.list()
# Paginate: client.inboxes.list(limit=20, page_token=inboxes.next_page_token)

# Get, update, delete
inbox = client.inboxes.get(inbox_id="support@agentmail.to")
client.inboxes.update(inbox_id="support@agentmail.to", display_name="New Name")
client.inboxes.delete(inbox_id="support@agentmail.to")
const inbox = await client.inboxes.create({
    username: "support",
    domain: "yourdomain.com",
    displayName: "Support Agent",
    clientId: "support-v1",
});

const inboxes = await client.inboxes.list();
const fetched = await client.inboxes.get("support@agentmail.to");
await client.inboxes.update("support@agentmail.to", { displayName: "New Name" });
await client.inboxes.delete("support@agentmail.to");

Custom domains require a paid plan. Default @agentmail.to inboxes are free.

Messages

Send

Always provide both text and html for best deliverability. Maximum 50 recipients across to + cc + bcc combined.

sent = client.inboxes.messages.send(
    inbox_id="agent@agentmail.to",
    to="recipient@example.com",       # string or list
    subject="Hello from AgentMail",
    text="Plain text body",
    html="<p>HTML body</p>",          # optional but recommended
    cc="cc@example.com",              # optional, string or list
    bcc="bcc@example.com",            # optional, string or list
    reply_to="replies@example.com",   # optional
    labels=["outreach"],              # optional
    attachments=[{                    # optional
        "filename": "report.pdf",
        "content": base64_content,    # Base64-encoded
        "content_type": "application/pdf",
    }],
)
# sent.message_id, sent.thread_id
const sent = await client.inboxes.messages.send("agent@agentmail.to", {
    to: "recipient@example.com",
    subject: "Hello from AgentMail",
    text: "Plain text body",
    html: "<p>HTML body</p>",
    cc: "cc@example.com",
    labels: ["outreach"],
    attachments: [{
        filename: "report.pdf",
        content: base64Content,
        contentType: "application/pdf",
    }],
});

List and get

# List messages in an inbox. Note: .list() returns MessageItem objects
# (metadata only — subject, from, labels, timestamps, etc.) with NO body
# content. To read .text / .html / .extracted_text you must fetch the full
# message with .get().
response = client.inboxes.messages.list(
    inbox_id="agent@agentmail.to",
    limit=10,                # optional, default varies
    labels=["unread"],       # optional, filter by label
)
for item in response.messages:
    # item is a MessageItem (metadata only). Fetch the full Message for body:
    msg = client.inboxes.messages.get(
        inbox_id=item.inbox_id,
        message_id=item.message_id,
    )
    # Use extracted_text for reply content without quoted history
    content = msg.extracted_text or msg.text
    print(msg.subject, content)

# Paginate
while response.next_page_token:
    response = client.inboxes.messages.list(
        inbox_id="agent@agentmail.to",
        page_token=response.next_page_token,
    )

# Get a specific message
msg = client.inboxes.messages.get(
    inbox_id="agent@agentmail.to",
    message_id="<abc123@agentmail.to>",
)

# Get raw MIME content
raw = client.inboxes.messages.get_raw(
    inbox_id="agent@agentmail.to",
    message_id="<abc123@agentmail.to>",
)
const response = await client.inboxes.messages.list("agent@agentmail.to", {
    limit: 10,
    labels: ["unread"],
});

const msg = await client.inboxes.messages.get(
    "agent@agentmail.to",
    "<abc123@agentmail.to>",
);

Important: when processing inbound replies, always use extracted_text / extracted_html instead of text / html. These fields strip quoted history and signatures, giving you only the new content. This is powered by Talon reply extraction.

Also note: some email clients (Gmail, Outlook) send forwards as HTML-only. Always treat html as the primary content source and text as optional.

Reply

Replying adds the message to the existing thread.

reply = client.inboxes.messages.reply(
    inbox_id="agent@agentmail.to",
    message_id="<abc123@agentmail.to>",
    text="Thanks for your email!",
    html="<p>Thanks for your email!</p>",   # optional
    attachments=[...],                       # optional
    reply_all=False,                         # optional, defaults to False
)
const reply = await client.inboxes.messages.reply(
    "agent@agentmail.to",
    "<abc123@agentmail.to>",
    { text: "Thanks for your email!" },
);

Forward

client.inboxes.messages.forward(
    inbox_id="agent@agentmail.to",
    message_id="<abc123@agentmail.to>",
    to="colleague@example.com",
    text="FYI, see below.",       # optional prepended text
)
await client.inboxes.messages.forward(
    "agent@agentmail.to",
    "<abc123@agentmail.to>",
    {
        to: "colleague@example.com",
        text: "FYI, see below.",
    },
);

Update labels

Use labels to track message processing state. AgentMail does not have a built-in "read/unread" flag. Use labels instead.

client.inboxes.messages.update(
    inbox_id="agent@agentmail.to",
    message_id="<abc123@agentmail.to>",
    add_labels=["processed", "replied"],
    remove_labels=["unread"],
)
await client.inboxes.messages.update(
    "agent@agentmail.to",
    "<abc123@agentmail.to>",
    {
        addLabels: ["processed", "replied"],
        removeLabels: ["unread"],
    },
);

Attachments

import base64

# Send with attachment
with open("report.pdf", "rb") as f:
    content = base64.b64encode(f.read()).decode()

client.inboxes.messages.send(
    inbox_id="agent@agentmail.to",
    to="user@example.com",
    subject="Report attached",
    text="See attached.",
    attachments=[{
        "filename": "report.pdf",
        "content": content,
        "content_type": "application/pdf",
    }],
)

# Retrieve attachment from received message
attachment = client.inboxes.messages.get_attachment(
    inbox_id="agent@agentmail.to",
    message_id="<abc123@agentmail.to>",
    attachment_id="att_456",
)
import { readFileSync } from "node:fs";

const content = readFileSync("report.pdf").toString("base64");

await client.inboxes.messages.send("agent@agentmail.to", {
    to: "user@example.com",
    subject: "Report attached",
    text: "See attached.",
    attachments: [{ filename: "report.pdf", content, contentType: "application/pdf" }],
});

const attachment = await client.inboxes.messages.getAttachment(
    "agent@agentmail.to",
    "<abc123@agentmail.to>",
    "att_456",
);

Threads

Threads group related messages in a conversation. When you send a new message, a thread is created. Replies are added to the same thread automatically.

# List threads in an inbox
threads = client.inboxes.threads.list(
    inbox_id="agent@agentmail.to",
    labels=["unreplied"],     # optional filter
)

# Get a specific thread with all messages
thread = client.inboxes.threads.get(
    inbox_id="agent@agentmail.to",
    thread_id="thd_123",
)
for msg in thread.messages:
    print(msg.subject, msg.extracted_text)

# Org-wide thread listing (across all inboxes)
all_threads = client.threads.list()

# Delete a thread
client.inboxes.threads.delete(
    inbox_id="agent@agentmail.to",
    thread_id="thd_123",
)
const threads = await client.inboxes.threads.list("agent@agentmail.to", {
    labels: ["unreplied"],
});

const thread = await client.inboxes.threads.get("agent@agentmail.to", "thd_123");

const allThreads = await client.threads.list();

Drafts

Create drafts for human-in-the-loop approval. The agent composes a draft, a human reviews, then the draft is sent.

# Create draft
draft = client.inboxes.drafts.create(
    inbox_id="agent@agentmail.to",
    to="recipient@example.com",
    subject="Pending approval",
    text="Draft content for review",
    html="<p>Draft content for review</p>",
)

# List drafts
drafts = client.inboxes.drafts.list(inbox_id="agent@agentmail.to")

# Get, update
draft = client.inboxes.drafts.get(inbox_id="agent@agentmail.to", draft_id=draft.draft_id)
client.inboxes.drafts.update(
    inbox_id="agent@agentmail.to",
    draft_id=draft.draft_id,
    text="Updated draft content",
)

# Send draft (converts to message, removes from drafts)
client.inboxes.drafts.send(inbox_id="agent@agentmail.to", draft_id=draft.draft_id)

# Delete draft without sending
client.inboxes.drafts.delete(inbox_id="agent@agentmail.to", draft_id=draft.draft_id)
const draft = await client.inboxes.drafts.create("agent@agentmail.to", {
    to: "recipient@example.com",
    subject: "Pending approval",
    text: "Draft content",
});

await client.inboxes.drafts.send("agent@agentmail.to", draft.draftId, {});

Pods (multi-tenant isolation)

Pods provide isolated environments for SaaS platforms. Each pod has its own set of inboxes.

# Create pod per customer
pod = client.pods.create(
    name="customer-acme",
    client_id="pod-acme-v1",   # idempotent
)

# Create inbox within pod (pods.inboxes.create accepts flat kwargs)
inbox = client.pods.inboxes.create(
    pod_id=pod.pod_id,
    username="notifications",
    client_id="acme-notifications-v1",
)

# List inboxes scoped to pod
inboxes = client.pods.inboxes.list(pod_id=pod.pod_id)

# List threads scoped to pod
threads = client.pods.threads.list(pod_id=pod.pod_id)

# List, get, delete pods
pods = client.pods.list()
pod = client.pods.get(pod_id=pod.pod_id)
client.pods.delete(pod_id=pod.pod_id)
const pod = await client.pods.create({ name: "customer-acme", clientId: "pod-acme-v1" });
const inbox = await client.pods.inboxes.create(pod.podId, {
    username: "notifications",
    clientId: "acme-notifications-v1",
});
const inboxes = await client.pods.inboxes.list(pod.podId);

Allow/block lists

Control which external senders can deliver to an inbox. Block list takes priority over allow list.

Lists are flat. Each entry is one (direction, type, entry) tuple — there is no batch update, no .allow / .block sub-namespace. direction is "send", "receive", or "reply". type is "allow" or "block".

# Allow a sender on incoming mail
client.inboxes.lists.create(
    inbox_id="agent@agentmail.to",
    direction="receive",
    type="allow",
    entry="boss@company.com",
)

# Block a sender on incoming mail
client.inboxes.lists.create(
    inbox_id="agent@agentmail.to",
    direction="receive",
    type="block",
    entry="spammer@example.com",
)

# List entries for one (direction, type) pair
allow = client.inboxes.lists.list(
    inbox_id="agent@agentmail.to",
    direction="receive",
    type="allow",
)

# Check a single entry
entry = client.inboxes.lists.get(
    inbox_id="agent@agentmail.to",
    direction="receive",
    type="allow",
    entry="boss@company.com",
)

# Remove an entry
client.inboxes.lists.delete(
    inbox_id="agent@agentmail.to",
    direction="receive",
    type="allow",
    entry="boss@company.com",
)
await client.inboxes.lists.create(
    "agent@agentmail.to",
    "receive",
    "allow",
    { entry: "boss@company.com" },
);

await client.inboxes.lists.create(
    "agent@agentmail.to",
    "receive",
    "block",
    { entry: "spammer@example.com" },
);

const allow = await client.inboxes.lists.list(
    "agent@agentmail.to",
    "receive",
    "allow",
);

await client.inboxes.lists.delete(
    "agent@agentmail.to",
    "receive",
    "allow",
    "boss@company.com",
);

Domains

Custom domains let agents send from your own domain (e.g., agent@yourdomain.com). SPF, DKIM, and DMARC records are auto-generated. Requires paid plan.

# Add domain. feedback_enabled is required: set True to route
# bounce/complaint notifications to your inboxes.
domain = client.domains.create(domain="yourdomain.com", feedback_enabled=True)
# domain.records -> list of VerificationRecord objects to add at your registrar

# Verify after DNS records are set
client.domains.verify(domain_id=domain.domain_id)

# List, get, delete
domains = client.domains.list()
domain = client.domains.get(domain_id=domain.domain_id)
client.domains.delete(domain_id=domain.domain_id)
const domain = await client.domains.create({
    domain: "yourdomain.com",
    feedbackEnabled: true,
});
await client.domains.verify(domain.domainId);

Real-time events

AgentMail supports both WebSockets and webhooks for real-time notifications. See references/webhooks.md and references/websockets.md for detailed setup and full code examples.

WebSockets (recommended for agents)

No public URL needed. Persistent connection with instant delivery.

Python (sync):

from agentmail import AgentMail, Subscribe, Subscribed, MessageReceivedEvent

client = AgentMail()
with client.websockets.connect() as socket:
    socket.send_subscribe(Subscribe(inbox_ids=["agent@agentmail.to"]))
    for event in socket:
        if isinstance(event, Subscribed):
            print(f"Subscribed to: {event.inbox_ids}")
        elif isinstance(event, MessageReceivedEvent):
            print(f"From: {event.message.from_}")
            print(f"Subject: {event.message.subject}")
            print(f"Body: {event.message.extracted_text}")

Python (async):

from agentmail import AsyncAgentMail, Subscribe, MessageReceivedEvent

client = AsyncAgentMail()
async with client.websockets.connect() as socket:
    await socket.send_subscribe(Subscribe(inbox_ids=["agent@agentmail.to"]))
    async for event in socket:
        if isinstance(event, MessageReceivedEvent):
            await process_email(event.message)

TypeScript:

const socket = await client.websockets.connect();
socket.on("open", () => {
    socket.sendSubscribe({ type: "subscribe", inboxIds: ["agent@agentmail.to"] });
});
socket.on("message", (event) => {
    // Use event.eventType (not event.type — event.type is always "event")
    if (event.eventType === "message.received") {
        // TypeScript uses .from directly; only Python needs .from_ (reserved keyword)
        console.log("From:", event.message.from);
        console.log("Subject:", event.message.subject);
    }
});

Webhooks

HTTP POST to your endpoint on email events. Requires a public URL.

event_types is required — you must pick at least one event to subscribe to. Pass an explicit list of every event you want to receive.

webhook = client.webhooks.create(
    url="https://your-server.com/webhooks",
    event_types=["message.received", "message.bounced"],
)
# webhook.webhook_id, webhook.secret

# List, get, delete
webhooks = client.webhooks.list()
client.webhooks.delete(webhook_id=webhook.webhook_id)

Typed webhook event types (listed in the SDK's Literal): message.received, message.sent, message.delivered, message.bounced, message.complained, message.rejected, domain.verified.

Runtime-only events — accepted by the API but not in the SDK's typed Literal — include message.received.spam and message.received.blocked. Pass them as plain strings if you need them. Type checkers will flag them; that's expected.

Always verify webhook signatures before processing. See references/webhooks.md.

Idempotency

Pass client_id / clientId on create operations to make them safe to retry:

from agentmail.inboxes.types import CreateInboxRequest

inbox = client.inboxes.create(
    request=CreateInboxRequest(client_id="my-unique-key"),
)
# Calling again with the same client_id returns the existing inbox, not a duplicate

pod = client.pods.create(client_id="pod-unique-key")
# pods.create takes flat kwargs; same idempotency behavior

Error handling

Both SDKs raise/throw on 4xx and 5xx responses. On 429 (rate limit), read the Retry-After header and use exponential backoff. Both SDKs retry automatically (default: 2 retries).

try:
    client.inboxes.messages.send(inbox_id, to="user@example.com", subject="Hi", text="Hello")
except Exception as e:
    print(f"Error: {e}")
    # e.body.message contains details if available

# Python: override retries per call via request_options
# (the AgentMail constructor has no max_retries argument)
client.inboxes.messages.send(
    inbox_id,
    to="user@example.com",
    subject="Hi",
    text="Hello",
    request_options={"max_retries": 5},
)
try {
    await client.inboxes.messages.send(inboxId, {
        to: "user@example.com",
        subject: "Hi",
        text: "Hello",
    });
} catch (err) {
    console.error("Error:", err.message);
    // err.statusCode, err.body for details
}

// TypeScript: override retries globally on the client, or per-call via requestOptions
const client = new AgentMailClient({ apiKey: "...", maxRetries: 5 });

IMAP and SMTP

AgentMail inboxes are accessible via standard IMAP and SMTP protocols, enabling integration with traditional email clients and legacy systems. See https://docs.agentmail.to/imap-smtp for setup details.

Pagination

All list endpoints use cursor-based pagination:

response = client.inboxes.messages.list(inbox_id, limit=20)
while response.next_page_token:
    response = client.inboxes.messages.list(
        inbox_id, limit=20, page_token=response.next_page_token
    )

Reference files

For detailed coverage of specific topics:

  • references/webhooks.md -- webhook setup, event types, payload structure, signature verification
  • references/websockets.md -- WebSocket connection, sync/async patterns, event handler pattern, subscribe options
  • references/full-api-reference.md -- complete endpoint and SDK method table with all parameters
Featured
CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
Keep your Mac awake
Keep your Mac awake
Keep your Mac awake while Claude Code and 40+ AI agents run. Sleeps when they're idle.
One time payment $9 →
Context.devContext.dev
Context.dev
Integrate web data into your AI product. One API to scrape website & brand data.
Get API Key Now →
Make your agent a DeFi expert
Make your agent a DeFi expert
Agent, run crypto. Access onchain data & trade routes via 1inch.
Install now →
Make money from your Skills
Make money from your Skills
On Capafy, your Skill runs online 24/7 as an agent product, and you get paid every time someone uses it.
Start earning →
AppSignal
AppSignal
Monitor with ease. Code with confidence.
Start Free Trial →
First SeenJun 3, 2026
View on GitHub

Recommended

caveman

juliusbrussee/caveman

Ultra-compressed communication mode cutting token usage ~75% while preserving technical accuracy.
203.4k
67.8k
grill-me

mattpocock/skills

Relentless interviewing skill that stress-tests plans and designs through systematic questioning.
250.9k
114.5k
improve

shadcn/improve

Survey any codebase as a senior advisor and produce prioritized, self-contained implementation plans for other models/agents to execute.
10
205
systematic-debugging

obra/superpowers

Structured debugging methodology that mandates root cause investigation before attempting any fixes.
124.6k
215.9k
karpathy-guidelines

forrestchang/andrej-karpathy-skills

Behavioral guidelines to reduce common LLM coding mistakes through explicit assumptions, simplicity, and verifiable success criteria.
13.9k
165.4k
find-skills

vercel-labs/skills

Discover and install specialized agent skills from the open ecosystem when users need extended capabilities.
1.8M
21.1k