SDKs and CLI
Install the CLI, TypeScript, or Python client, authenticate once, and look up any operation's exact call on each surface.
Installation
npm install -g agentmail-cliThe CLI prebuilt binaries for macOS, Linux, and Windows are on the releases page.
There is also a Go SDK (agentmail-go), installed with go get github.com/agentmail-to/agentmail-go. The examples on this page show the CLI, TypeScript, and Python.
All three read your API key from the AGENTMAIL_API_KEY environment variable, so exporting it once is enough. You can also pass it explicitly: --api-key on any CLI command, apiKey in the TypeScript constructor, api_key in the Python one. Make a first call to check that the key works. The SDK call here, auth.me, returns the key’s identity: which organization, pod, or inbox it is scoped to, which is how code holding a key discovers what it can act on.
export AGENTMAIL_API_KEY="<API_KEY>"
agentmail inboxes listThe rest of this page is a reference, one section per resource area. Each operation gets its exact call on every surface, and the linked task page covers the parameters, responses, and errors in depth. The TypeScript and Python snippets reuse the client from above.
SDK/CLI Reference
Inboxes
An inbox is an email address your agent owns. The Quickstart walks through creating your first one and using it.
Create an inbox. Omit username and AgentMail generates the address:
agentmail inboxes create --display-name "Support agent"List your inboxes, or get one by its inbox_id. Each inbox’s address is in the email field of what comes back:
agentmail inboxes list
agentmail inboxes get --inbox-id "example@agentmail.to"Create and update also take a custom metadata map for your own bookkeeping (tenant ids, feature flags): string, number, or boolean values, up to 256 keys, keys and string values up to 256 characters. An update merges the map you send, a null value removes that key, and "metadata": null clears the whole map.
Update an inbox’s display_name, the sender name recipients see (details). The change applies to mail sent afterward:
agentmail inboxes update \
--inbox-id "example@agentmail.to" \
--display-name "Acme Updates"Delete an inbox. This permanently removes its messages, threads, and drafts:
agentmail inboxes delete --inbox-id "example@agentmail.to"Messages
For depth, Receive email has the inbound calls and Send and reply the outbound ones.
Send an email. It returns the new message’s message_id and the thread_id of the conversation it starts, which you keep to reply or follow up later:
agentmail inboxes:messages send \
--inbox-id "example@agentmail.to" \
--to "you@example.com" \
--subject "Your receipt" \
--text "Thanks for your order."List an inbox’s messages, newest first with optional labels and time filters, then get one by id to read its full body:
agentmail inboxes:messages list --inbox-id "example@agentmail.to"
agentmail inboxes:messages get \
--inbox-id "example@agentmail.to" \
--message-id "<message_id>"Reply to a message, or forward it to someone new with its attachments riding along. Reply-all (reply-all, replyAll, reply_all) takes the same inputs as reply and answers everyone on the thread:
agentmail inboxes:messages reply \
--inbox-id "example@agentmail.to" \
--message-id "<message_id>" \
--text "Thanks, got your message."
agentmail inboxes:messages forward \
--inbox-id "example@agentmail.to" \
--message-id "<message_id>" \
--to "teammate@example.com" \
--text "Forwarding for your records."Update a message’s labels, the state an agent leaves on mail it has handled (label patterns):
agentmail inboxes:messages update \
--inbox-id "example@agentmail.to" \
--message-id "<message_id>" \
--add-labels triaged \
--remove-labels unreadDownload an attachment. The call returns short-lived links to the file rather than bytes (details):
agentmail inboxes:messages get-attachment \
--inbox-id "example@agentmail.to" \
--message-id "<message_id>" \
--attachment-id "<attachment_id>"Threads
A thread is a whole conversation, the message that started it plus every reply. The full detail is on Manage conversations.
List the threads in an inbox, one summary per conversation:
agentmail inboxes:threads list --inbox-id "example@agentmail.to"Read a whole thread by its thread_id, every message included, oldest first:
agentmail threads get --thread-id "<thread_id>"Search threads with free text, ranked by relevance across senders, recipients, subjects, and message bodies. The same search exists for messages as inboxes:messages search (client.inboxes.messages.search):
agentmail inboxes:threads search \
--inbox-id "example@agentmail.to" \
-q "refund"Drop the inbox to list or search across every inbox your key’s scope covers, the view for a supervisor agent or a dashboard:
agentmail threads list
agentmail threads search -q "refund"Drafts
A draft is a saved, unsent message. The lifecycle is on Schedule an email with a draft.
Create a draft. Give it a send_at time and AgentMail delivers it at that time, with no scheduler on your side. In TypeScript a draft’s recipient fields take arrays, even for a single address:
agentmail inboxes:drafts create \
--inbox-id "example@agentmail.to" \
--to "you@example.com" \
--subject "Following up" \
--text "Checking in ahead of our call tomorrow." \
--send-at "2026-08-26T09:00:00Z"List an inbox’s drafts to see what is written or queued:
agentmail inboxes:drafts list --inbox-id "example@agentmail.to"Send a draft immediately, the approval step when a human reviews what an agent wrote. In TypeScript the request object is required, so pass {} when you have no label edits:
agentmail inboxes:drafts send \
--inbox-id "example@agentmail.to" \
--draft-id "<draft_id>"Delete a draft, which also cancels a scheduled send:
agentmail inboxes:drafts delete \
--inbox-id "example@agentmail.to" \
--draft-id "<draft_id>"Webhooks
A webhook pushes signed events to your HTTPS endpoint the moment mail arrives, bounces, or gets opened. The event catalog, payloads, and delivery contract are on Webhooks.
Create a webhook. The response includes the secret your receiver verifies delivery signatures with. AgentMail delivers through Svix, and the svix libraries for TypeScript and Python do the verification in one call:
agentmail webhooks create \
--url "https://worker.example.com/webhooks" \
--event-type message.received \
--event-type message.bounced \
--client-id "inbound-agent-v1"Both SDKs export a typed class for every payload shape, so a receiver can work with typed fields instead of raw JSON: serialization.events.MessageReceivedEvent.parse(payload) in TypeScript, and MessageReceivedEvent(**payload) imported from agentmail in Python.
List your webhooks, or get one by id to read its secret again:
agentmail webhooks list
agentmail webhooks get --webhook-id "<webhook_id>"Update a webhook. event_types replaces the subscribed list in full, so send every type you want after the change:
agentmail webhooks update \
--webhook-id "<webhook_id>" \
--event-type '["message.received", "message.delivered"]'Delete a webhook, which stops its deliveries immediately:
agentmail webhooks delete --webhook-id "<webhook_id>"The TypeScript and Python SDKs also include WebSocket clients that stream the same events over a persistent connection, so agents without a public URL can subscribe too. See WebSockets.
Domains
A verified custom domain lets your agents send and receive as support@example.com. Custom domains walks through the DNS records, verification statuses, and troubleshooting.
Register a domain. The response carries the exact DNS records to publish:
agentmail domains create --domain "example.com"Request verification once the records are published, then get the domain to watch its status. Inboxes can be created on it when the status reads VERIFIED:
agentmail domains verify --domain-id "example.com"
agentmail domains get --domain-id "example.com"Update a domain’s settings: bounce and complaint feedback, inboxes on any subdomain, and open tracking:
agentmail domains update \
--domain-id "example.com" \
--subdomains-enabled=trueList your registered domains, or delete one permanently:
agentmail domains list
agentmail domains delete --domain-id "example.com"Allow and block lists
Each inbox has an allow and a block list per direction (receive, reply, send) that decide whose mail gets through. Control who can email your agent covers how entries match and interact.
Add an entry. An entry containing @ matches one address, anything else matches a whole domain:
agentmail inboxes:lists create \
--inbox-id "example@agentmail.to" \
--direction receive \
--type block \
--entry blocked-sender@example.com \
--reason "unwanted newsletter"List one list’s entries to see what filtering is active:
agentmail inboxes:lists list \
--inbox-id "example@agentmail.to" \
--direction receive \
--type blockRemove an entry, identified by the same direction, type, and value it was created with:
agentmail inboxes:lists delete \
--inbox-id "example@agentmail.to" \
--direction receive \
--type block \
--entry blocked-sender@example.comPods
A pod is an isolated workspace inside your organization, usually one per tenant. Build a multi-tenant platform shows the full provisioning and offboarding flow.
Create a pod. client_id is your own tenant id and makes the create idempotent, so a retry returns the existing pod instead of a duplicate:
agentmail pods create \
--name "Acme Corp" \
--client-id "customer-042"List your pods, or get one by its pod_id:
agentmail pods list
agentmail pods get --pod-id "<pod_id>"Work with a pod’s own resources through the pod routes. Inboxes, threads, drafts, domains, lists, webhooks, API keys, and metrics all exist under the pod (pods:inboxes, pods:domains, and so on in the CLI, client.pods.inboxes and siblings in the SDKs), which is how you get per-tenant views like every unread conversation in one pod:
agentmail pods:threads list \
--pod-id "<pod_id>" \
--label unreadDelete a pod once it is empty:
agentmail pods delete --pod-id "<pod_id>"API keys
Create an organization-level key. The full api_key value is returned only this once, so store it right away. A lost key cannot be read again, only deleted and replaced:
agentmail api-keys create --name "worker-key"List your keys, or delete one by its api_key_id:
agentmail api-keys list
agentmail api-keys delete --api-key-id "<api_key_id>"Mint scoped keys under an inbox or a pod. An inbox key can act only on its inbox and a pod key only inside its pod, the pattern for handing a key to a single agent or a tenant’s service:
agentmail inboxes:api-keys create \
--inbox-id "example@agentmail.to" \
--name "agent-key"
agentmail pods:api-keys create \
--pod-id "<pod_id>" \
--name "tenant-key"Metrics and usage
Two read-only series describe your account: event counts for what happened in a window, and usage totals for what a scope holds over time. Plans and Usage Tracking covers the parameters, scopes, and permissions.
Count email events over a window, or read running totals of storage, messages, and resources:
const events = await client.metrics.queryEvents({
eventTypes: ["message.sent", "message.bounced"],
period: 3600,
});
const usage = await client.metrics.queryUsage({
usageTypes: ["storage_bytes", "message_count"],
period: 86400,
});Read your organization’s effective limits next to its current counts. These are the values enforcement uses, add-ons and grants included, so compare them before a batch of creates to catch a cap early:
agentmail organizations getAgent sign-up
An agent can create its own organization, inbox, and API key with one call, no Console needed. Sign-up is for first-time users only, and the returned api_key is shown just this once. A 6-digit code goes to the human’s email, and verifying with it (authenticated with the new key) lifts the pre-verification limits. The Quickstart walks through the flow:
agentmail agent sign-up \
--human-email "you@example.com" \
--username "my-agent"
# returns api_key, inbox_id, and organization_id
export AGENTMAIL_API_KEY="<api_key from the response>"
agentmail agent verify --otp-code "<6-digit code>"Client configuration
The CLI and SDKs add the v0/ API prefix to every request themselves, so a base URL must always be the bare host, https://api.agentmail.to. You only set a base URL when you are pointing at something other than the default US production host, for example the EU region https://api.agentmail.eu:
- CLI: the
--base-urlflag on any command - TypeScript: the
baseUrlconstructor option, orenvironmentwith a predefined region likeAgentMailEnvironment.EuProd - Python: the
environmentconstructor option, either a predefined region likeAgentMailEnvironment.EU_PRODor a customAgentMailEnvironment(http=..., websockets=...)
Both SDKs retry failed requests automatically with exponential backoff: 2 retries by default, on 408, 429, and 5xx responses, waiting out a Retry-After header first. The request timeout defaults to 60 seconds. Every SDK method also takes per-request options, a final argument in TypeScript and request_options in Python, which override retries, timeout, and headers for that one call. The idempotency key on sends travels the same way.
# base URL must be the bare host, no /v0
agentmail --base-url "https://api.agentmail.to" inboxes list
# output format and GJSON transforms
agentmail inboxes list --format pretty
agentmail inboxes list --transform "inboxes.#.inbox_id"The CLI prints JSON by default. --format switches the output (pretty for a human-readable view, yaml, or explore for an interactive one), --transform filters it with GJSON syntax, and --debug logs each HTTP request and response, the quickest way to see what a command sent.
Pagination
Every list endpoint pages the same way. A page carries count (entries in this page) and, when more remain, a next_page_token. Pass that token back as page_token to get the next page, and stop when the response has no next_page_token. limit caps the page size, and most lists also take ascending to flip to oldest first.
To drain a whole list, loop until the token is gone:
# the CLI takes the same parameters; pass the previous page's token
agentmail inboxes list --limit 50
agentmail inboxes list --limit 50 --page-token "<next_page_token from the previous page>"The same loop works on any list: swap inboxes for threads, messages, drafts, webhooks, domains, or pods, and read the matching array field from each page.