Email for AI agents: threading, delivery states and sending limits
· Markdown
What an agent needs from email beyond SMTP: a mailbox per agent, correct threading, delivery and bounce signals as events, search, and sending limits that stop an agent from abusing the service.
What changes when the sender is an agent
A person sends a few emails and notices when one bounces. An agent can send thousands, never notices a bounce unless told, and can be pointed at a stranger by a prompt injection. Email for agents therefore needs three things a human mail client does not: structured delivery signals, hard limits, and a clean way to wait for a reply.
One mailbox per identity, created together
When an identity is created its mailbox exists immediately. The address is derived from the handle and the sending domain, so there is no separate provisioning step that can be forgotten or fail halfway.
Sending, replying and forwarding
// New message with bcc.
await client.mail.send({
identityId: agent.id,
to: ["customer@example.com"],
bcc: ["audit@yourcompany.com"],
subject: "We got your ticket",
text: "Someone will be with you shortly.",
});
// Reply in the same thread, to everyone.
await client.mail.send({
identityId: agent.id,
inReplyToMessageId: messageId,
replyAll: true,
text: "Update: this is resolved.",
});
// Forward an existing message.
await client.mail.forward({ identityId: agent.id, messageId, to: ["teammate@yourcompany.com"], text: "FYI" });Sending is asynchronous. A 202 means the message is queued, not delivered. The outcome arrives later as an event.
Delivery states are events, not log lines
- mail.sent: the provider accepted the message.
- mail.delivered: the provider confirmed delivery.
- mail.bounced: the provider reported a bounce.
- mail.complained: the recipient marked it as spam.
- mail.failed: the provider refused it or could not be reached, with a detail field saying why.
- mail.received: an inbound message was ingested for the mailbox.
Because these are events on the same durable log as everything else, an agent can react: stop sending to an address that bounced, or escalate when a complaint arrives, without scraping anything.
Threads, folders and search
Messages are grouped into threads. A thread listing supports folders (inbox, sent, archive and others), read and starred state, a date range with an explicit time zone, and cursor pagination that stays stable while new mail arrives. Search is full text and supports quoted phrases and exclusions, for example a query of purchase order minus draft.
const page = await client.mail.listThreads({ identityId: agent.id, folder: "inbox", isRead: false });
await client.mail.updateThread(agent.id, page.items[0]!.id, { isRead: true, folder: "archive" });
const hits = await client.mail.search(agent.id, { q: '"purchase order" -draft' });Limits that make open signup safe
Before an organization's owner is verified it can only email its owner, and only a few messages a day. Sending anywhere else fails with the error code mail.unverified_recipient. After verification the organization has a daily sending limit; client.usage() reports what has been used, when it resets, and how many agents the organization has against its cap. A send that is refused is not counted against the limit.
Idempotent sends
Retries are the most common cause of duplicate emails. Sending the same Idempotency-Key header with the same request returns the original outcome instead of sending again, and the response marks it with an x-idempotent-replay header. Reusing a key with a different body is an error rather than a silent second send. In the SDK, pass idempotencyKey to mail.send.
Read more
- Mail overview: https://www.agent-identity.dev/docs/mail
- Sending: https://www.agent-identity.dev/docs/mail/sending
- Receiving: https://www.agent-identity.dev/docs/mail/receiving
- Attachments: https://www.agent-identity.dev/docs/mail/attachments
- Custom domains: https://www.agent-identity.dev/docs/mail/domains
Give your agent its own inbox in a couple of minutes. Get started → · Docs · llms.txt