Skip to content
AgentMail
AgentMail
Core tasks

Receive email

List inbound threads, read new reply content, and download attachments from an AgentMail inbox.

Receive email by polling threads in an AgentMail inbox. This page covers pull-based receiving. Webhook and WebSocket events such as message.received are configured separately.

All requests use https://api.agentmail.to/v0 with Authorization: Bearer $AGENTMAIL_API_KEY.

Create or choose an inbox

Create an inbox to receive into. Omit username to generate one, and omit domain to use agentmail.to. Custom domains must be verified first; enabled subdomains of a verified domain also work.

agentmail inboxes create \
  --username support \
  --domain agentmail.to
Sample response
{
  "pod_id": "<pod_id>",
  "inbox_id": "support@agentmail.to",
  "email": "support@agentmail.to",
  "created_at": "2026-07-30T09:12:44Z",
  "updated_at": "2026-07-30T09:12:44Z"
}

The returned inbox_id is the inbox’s email address; pass it wherever an endpoint takes inbox_id. In Python, create fields are wrapped in CreateInboxRequest, imported from agentmail.inboxes.

Understand inbound messages

AgentMail stores each email as a message in a thread. A new email starts a thread; replies join it. Inbound messages get received and unread; classification can add labels:

LabelApplied when
receivedEvery inbound message. This system label cannot be changed through the API.
unreadEvery inbound message. Remove it after processing.
spamThe message failed spam screening.
unauthenticatedSPF, DKIM, and DMARC headers were all missing.
blockedThe sender address or domain matches a block-list entry.

Messages with present but failing SPF, DKIM, or DMARC are dropped before delivery and cannot be retrieved. Messages with missing authentication headers are delivered with unauthenticated.

Expected email can look missing. Thread listings hide spam, unauthenticated, blocked, and trashed items by default. Use the relevant include_* flag, such as include_spam=true, and an API key with the matching label-read permission, such as label_spam_read.

List unread threads

List threads visible to your API key, newest first, and filter by the unread label to find conversations your agent has not processed yet. To scope to a single inbox, use GET /inboxes/{inbox_id}/threads instead of GET /threads.

agentmail threads list --label unread
Sample response
{
  "count": 1,
  "threads": [
    {
      "inbox_id": "support@agentmail.to",
      "thread_id": "<thread_id>",
      "labels": ["received", "unread"],
      "timestamp": "2026-07-30T10:03:12Z",
      "senders": ["customer@example.com"],
      "recipients": ["support@agentmail.to"],
      "subject": "Order 4821 never arrived",
      "preview": "Hi, I placed order 4821 two weeks ago and...",
      "message_count": 1,
      "last_message_id": "<message_id>"
    }
  ]
}

Threads are ordered newest first. Page with limit and page_token; when more results remain, the response includes next_page_token.

Filtering by senders, recipients, or subject matches word prefixes, not arbitrary substrings, and caps limit at 100. A full email address can return no results while its username prefix matches, so filter on the username portion. Digit-only values are parsed as numbers and return 400; include a non-digit character.

Fetch and mark a thread processed

Fetch a thread to read its messages. messages[] is ordered oldest first, so the last item is the newest message.

agentmail threads get --thread-id thread_123
Sample response
{
  "inbox_id": "support@agentmail.to",
  "thread_id": "<thread_id>",
  "labels": ["received", "unread"],
  "subject": "Order 4821 never arrived",
  "messages": [
    {
      "message_id": "<message_id>",
      "from": "customer@example.com",
      "to": ["support@agentmail.to"],
      "subject": "Order 4821 never arrived",
      "text": "Hi, I placed order 4821 two weeks ago and...",
      "labels": ["received", "unread"],
      "timestamp": "2026-07-30T10:03:12Z"
    }
  ]
}

An unknown thread_id returns 404 with {"name": "NotFoundError", "message": "Thread not found"}. Branch on HTTP status and the PascalCase name, not the message.

After processing a message, remove its unread label (and optionally add read) so the next unread poll skips it:

curl -X PATCH "https://api.agentmail.to/v0/inboxes/support@agentmail.to/messages/<message_id>" \
  -H "Authorization: Bearer $AGENTMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"add_labels": ["read"], "remove_labels": ["unread"]}'

Read only the new reply content

text and html contain the full sent body, including quoted history. extracted_text and extracted_html contain new content with quoted history stripped by Talon. Use extracted_text for a reply, then fall back to text; text can be absent for HTML-only email, so use html last.

agentmail threads get --thread-id thread_123 \
  | jq -r '.messages[-1].extracted_text // .messages[-1].text // .messages[-1].html'

Download attachments and raw email

Thread and message objects carry attachment metadata, not bytes. Get an attachment to receive a signed download URL, then fetch it before the URL expires.

agentmail threads get-attachment \
  --thread-id thread_123 \
  --attachment-id att_456
Sample response
{
  "attachment_id": "att_456",
  "filename": "invoice.pdf",
  "size": 48213,
  "content_type": "application/pdf",
  "download_url": "https://…signed URL…",
  "expires_at": "2026-07-30T11:03:12Z"
}

The per-message endpoint is GET /v0/inboxes/{inbox_id}/messages/{message_id}/attachments/{attachment_id}.

For original headers, MIME structure, or signature verification, fetch the raw .eml file. The response is the same shape: a signed download_url with size and expires_at.

agentmail inboxes:messages get-raw \
  --inbox-id support@agentmail.to \
  --message-id msg_789
Was this page helpful?Suggest editsRaise issue