Skip to content
AgentMail
AgentMail
Core tasks

Manage conversations

Fetch full threads, track agent state with labels, and search email at inbox, pod, or organization scope.

Every message belongs to a thread. Replies attach through in_reply_to, so you can fetch, label, and search a conversation as one resource.

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

Fetch a thread

Fetch a thread to get its metadata and every message in the conversation. Messages are ordered oldest first, so the final element is the newest; last_message_id identifies it without reading messages.

agentmail threads get \
  --thread-id "$THREAD_ID"
Sample response
{
  "inbox_id": "agent@yourdomain.com",
  "thread_id": "<thread_id>",
  "labels": ["sent", "received", "unread"],
  "senders": ["customer@example.com"],
  "recipients": ["agent@yourdomain.com"],
  "subject": "Invoice 1042",
  "preview": "Thanks, payment confirmed...",
  "timestamp": "2026-07-30T10:03:12Z",
  "message_count": 2,
  "last_message_id": "<message_id>",
  "messages": [
    { "message_id": "<message_id>", "from": "agent@yourdomain.com", "text": "Invoice 1042 is attached.", "labels": ["sent"], "timestamp": "2026-07-29T16:40:00Z" },
    { "message_id": "<message_id>", "from": "customer@example.com", "text": "Thanks, payment confirmed.", "labels": ["received", "unread"], "timestamp": "2026-07-30T10:03:12Z" }
  ]
}

Thread labels is the union of labels on all messages; timestamp is the newest message’s time, and senders and recipients collect every address in the conversation.

To find threads instead of fetching one, list them — newest first, with a labels filter and next_page_token paging. Listing scope comes from the API key; use GET /inboxes/{inbox_id}/threads or GET /pods/{pod_id}/threads to set it explicitly. The senders, recipients, and subject filters are served by search and cap results at 100.

Track agent state with labels

Use custom labels to record agent state on a conversation — triaged after classification, escalated for human handoff — then filter list endpoints by labels to build work queues. System labels (received, sent, bounced, complained, delayed, delivered, rejected, opened, and scheduled) are set by the delivery pipeline and cannot be changed through the API.

Update a thread’s labels with add_labels and remove_labels; the change applies to every message in the thread. A common pattern is one request that removes unread and adds your state label.

curl -X PATCH "https://api.agentmail.to/v0/threads/$THREAD_ID" \
  -H "Authorization: Bearer $AGENTMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"add_labels": ["triaged"], "remove_labels": ["unread"]}'
Sample response
{
  "inbox_id": "agent@yourdomain.com",
  "thread_id": "<thread_id>",
  "labels": ["sent", "received", "triaged"]
}
  • Labels are trimmed and lowercased on write; each is limited to 256 characters.
  • A system label in add_labels or remove_labels returns 400 ValidationError.
  • When a label is in both fields, removal wins.
  • Updates return 422 UnprocessableError for threads with 100 or more messages. Update individual messages with PATCH /inboxes/{inbox_id}/messages/{message_id}, or up to 50 at once with POST /inboxes/{inbox_id}/messages/batch-update.

Search conversations

Thread search is relevance-ranked full-text search. It matches senders, recipients, and subject as substrings, and message bodies as tokenized full text.

ScopeEndpoint
OrganizationGET /threads/search
PodGET /pods/{pod_id}/threads/search
InboxGET /inboxes/{inbox_id}/threads/search
agentmail threads search -q "invoice 1042"
Sample response
{
  "count": 1,
  "threads": [
    {
      "thread_id": "<thread_id>",
      "subject": "Invoice 1042",
      "preview": "Thanks, payment confirmed...",
      "highlights": {
        "subject": ["**Invoice** **1042**"],
        "text": ["your **invoice** **1042** is attached"]
      }
    }
  ]
}

Results are ranked by relevance, not recency. A highlights key appears only when that field matched, with matched terms wrapped in **. limit defaults to 50 and cannot exceed 100; page with page_token and bound time with before or after. Search always excludes spam, trash, blocked, and unauthenticated threads.

Message search exists at inbox scope only (GET /inboxes/{inbox_id}/messages/search). For content across inboxes, search organization-scope threads, then fetch their messages.

Use list filters for sender or subject results ordered by recency. Use search for body content or relevance ranking.

Handle concurrent updates

Simultaneous replies or a label update racing an inbound message can produce HTTP 409 RaceConditionError. Retry the request. Label updates converge on retry because they add and remove labels rather than replace the label set. If the retry depends on the current labels, fetch the thread first.

Was this page helpful?Suggest editsRaise issue