This is architectural guidance for agents that need to send and receive email, not SDK documentation. You get seven opinionated patterns: one inbox per agent (never share), two-way conversation loops using extracted_text to strip quoted replies, human-in-the-loop drafts for high-stakes messages, WebSockets over polling, multi-agent topologies with clear role separation, OTP extraction for verification flows, and labels for workflow state. The security section is brief but covers the critical stuff like prompt injection via inbound email and allow lists for production inboxes. Useful if you're building support bots, notification agents, or anything that needs structured email communication instead of just fire-and-forget sending.
npx -y skills add agentmail-to/agentmail-skills --skill agent-email-patterns --agent claude-codeInstalls into .claude/skills of the current project.
Opinionated patterns for building AI agents that communicate over email. This skill covers architecture and security decisions, not SDK specifics. For AgentMail SDK usage, use the agentmail skill.
Giving an agent OAuth access to a human's Gmail account is the most common approach and the most dangerous:
gmail.modify) grant read/send/delete over the entire mailbox history, far beyond what any single task needsThe safer default: one dedicated, API-native inbox per agent (see Pattern 1).
Durable architectural constraints when choosing infrastructure (not a ranking):
| Provider | Key constraint |
|---|---|
| Gmail API | No programmatic inbox creation; no WebSocket push (Pub/Sub or polling only); access is revocable by Google at any time |
| Resend | No threads or conversation concept; cannot list/search received messages; inbound only via webhook, no persistent inbox |
| SendGrid | Inbound parse is stateless; no thread management; no programmatic inbox creation |
| Amazon SES | Inbound is rule-based (S3/Lambda triggers), not a mailbox; no thread management; no WebSocket support |
Every agent gets its own email address. Never share inboxes between agents.
client.inboxes.create(request=CreateInboxRequest(username="support-agent", client_id="support-v1"))
Why: clear sender identity, isolation (agents can't read each other's mail), per-agent auditability, and blast-radius containment if one agent is compromised.
Anti-pattern: one shared inbox with multiple agents reading from it. This creates race conditions and makes debugging impossible.
The core agent email pattern: agent sends, human replies, agent reads the reply and responds, looping until resolved.
Gotchas:
messages.list() returns metadata only (no body) -- call .get() on each item to fetch .text / .extracted_text.extracted_text / extracted_html for inbound replies so you don't reprocess the entire quoted chain on every turn.messages.reply(inbox_id, message_id, ...) with the parent message_id -- there is no thread_id parameter; AgentMail threads it automatically from the parent message.For high-stakes emails, let the agent draft and a human approve before sending: drafts.create(...) then drafts.send(inbox_id, draft_id).
Use drafts when:
Send directly when:
Default to event-driven delivery (WebSockets or webhooks) rather than polling. Polling is acceptable when neither is workable — e.g. a constrained environment with no public URL and no persistent connection — but expect higher latency and API usage.
| Factor | WebSockets | Webhooks |
|---|---|---|
| Public URL needed | No | Yes |
| Best for | Agents, bots, local dev | Servers, serverless |
| Latency | Lowest (persistent) | HTTP round-trip |
| Reconnection | You handle it | AgentMail retries |
Webhook payloads must be verified before use -- see references/threat-model.md.
For systems with multiple agents, assign clear roles (e.g. support@, sales@, billing@, router@) and use allow lists (references/threat-model.md) to restrict which external senders can reach each agent. For hub-and-spoke, peer-to-peer, and hierarchical escalation patterns, see references/topologies.md.
Agents that sign up for services need to receive and extract verification codes (e.g. regex for a 4-8 digit code in the inbound message text).
This applies to explicitly authorized first-party or test flows only -- e.g. your own agent signing up for a service it will operate, or a test account you control. It does not authorize automating sign-in, verification, or account-recovery flows for third-party accounts, or bypassing a service's terms of use or human-consent requirements.
Best practices:
Use labels to track message processing state within an inbox (add_labels / remove_labels on messages.update, then filter with messages.list(..., labels=[...])).
Common label schemes:
unread / processed / archivedneeds-reply / replied / escalatedbilling / support / sales (category routing)See references/threat-model.md for the full threat model. Critical rules:
references/threat-model.md.references/topologies.md -- hub-and-spoke, peer-to-peer, hierarchical, and multi-tenant pod agent email architecturesreferences/threat-model.md -- prompt injection, webhook spoofing, OAuth/credential exposure, data leakage, inbox enumeration, and the authorization matrixsickn33/antigravity-awesome-skills
moizibnyousaf/ai-agent-skills
github/awesome-copilot