# Receive email (/core/receive)

<!-- agent-signals: reading_time_min: 6 · est_tokens: 2400 · updated: 2026-09-06 -->
Related: [Send and reply](/core/send.md), [Manage conversations](/core/conversations.md), [Control who can email your agent](/core/inbound-control.md), [Connect external mail tools: IMAP and SMTP](/core/imap-smtp.md), [Architecture](/architecture.md)



# Receive email in an inbox

Listing an inbox returns message summaries for triage, getting a message by id returns the full body, and the attachment call returns download links. Use this to poll an inbox and act on incoming mail.

## Do this

1. List the messages that still need handling:

```bash
curl "https://api.agentmail.to/v0/inboxes/example@agentmail.to/messages?labels=unread" \
  -H "Authorization: Bearer $AGENTMAIL_API_KEY"
```

2. Read one message in full. URL-encode the `message_id` from the listing:

```bash
curl "https://api.agentmail.to/v0/inboxes/example@agentmail.to/messages/<url_encoded_message_id>" \
  -H "Authorization: Bearer $AGENTMAIL_API_KEY"
```

Read the body from `extracted_text` (new content only, quoted history stripped), then fall back to `text`, then `html`.

3. Download an attachment listed in the message's `attachments` array:

```bash
curl "https://api.agentmail.to/v0/inboxes/example@agentmail.to/messages/<url_encoded_message_id>/attachments/<attachment_id>" \
  -H "Authorization: Bearer $AGENTMAIL_API_KEY"
# fetch the returned download_url with any HTTP client, no Authorization header needed
```

4. After the work is done, mark the message so the next `unread` poll skips it:

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

## SDK

```bash
npm install agentmail        # TypeScript
pip install agentmail        # Python
npm install -g agentmail-cli # CLI
```

* List messages: TypeScript `client.inboxes.messages.list("example@agentmail.to", { labels: ["unread"] })`, Python `client.inboxes.messages.list(inbox_id="example@agentmail.to", labels=["unread"])`, CLI `agentmail inboxes:messages list --inbox-id "example@agentmail.to" --label unread`
* Get one message: TypeScript `client.inboxes.messages.get(inboxId, messageId)`, Python `client.inboxes.messages.get(inbox_id=..., message_id=...)`, CLI `agentmail inboxes:messages get --inbox-id ... --message-id ...`
* Get attachment links: TypeScript `client.inboxes.messages.getAttachment(inboxId, messageId, attachmentId)`, Python `client.inboxes.messages.get_attachment(inbox_id=..., message_id=..., attachment_id=...)`, CLI `agentmail inboxes:messages get-attachment`
* Update labels: TypeScript `client.inboxes.messages.update(inboxId, messageId, { removeLabels: ["unread"] })`, Python `client.inboxes.messages.update(inbox_id=..., message_id=..., remove_labels=["unread"])`, CLI `agentmail inboxes:messages update --remove-labels unread`

The SDKs and CLI URL-encode `message_id` automatically. Full reference: [/integrations/sdks-and-cli](/integrations/sdks-and-cli).

## Facts

* `GET https://api.agentmail.to/v0/inboxes/{inbox_id}/messages` lists sent and received messages alike, newest first. `ascending=true` returns oldest first.
* List parameters: `limit`, `page_token`, `labels`, `before`, `after`, `from`, `to`, `subject`, `include_spam`, `include_blocked`, `include_unauthenticated`, `include_trash`.
* A listed message must carry every label passed in `labels`. `before` and `after` take full RFC 3339 timestamps like `2026-08-24T00:00:00Z`.
* The `from`, `to`, and `subject` filters are repeatable and every value must match. The `to` filter matches recipients in `to`, `cc`, or `bcc`.
* `from`, `to`, and `subject` match whole words and word prefixes. `subject=metrics` finds "Weekly metrics report". `subject=etrics` finds nothing.
* A list response carries `count` (messages in the current page) and, when more remain, `next_page_token`. Pass `next_page_token` as `page_token` to resume.
* `limit` above 100 fails validation when the request also has a `labels`, `from`, `to`, or `subject` filter.
* `preview` is the first 200 characters of the plain-text body, with no formatting.
* `GET https://api.agentmail.to/v0/inboxes/{inbox_id}/messages/{message_id}` returns the full message. Body fields: `text` (as sent, quoted history included), `html`, `extracted_text` and `extracted_html` (new content only).
* A `message_id` contains `<`, `>`, and `@`. URL-encode it in paths built by hand. An unencoded id fails with a `400`.
* Direction labels: `received` arrived from outside, `sent` went out from the inbox. Every received message starts with `unread`.
* Hidden labels and their include flags: `spam` (`include_spam`), `blocked` (`include_blocked`), `unauthenticated` (`include_unauthenticated`), `trash` (`include_trash`).
* `spam` means the message failed spam screening on arrival. `blocked` means the sender is barred by inbound rules. `unauthenticated` means the sender could not be verified (usually no SPF, DKIM, or DMARC). `trash` is added by the update call.
* Rejected before delivery and never stored: messages carrying a virus, and messages that fail DMARC while the sender's DMARC policy is `quarantine` or `reject`.
* On an enforcing DMARC failure, an ARC chain sealed by google.com, microsoft.com, outlook.com, or icloud.com substitutes the original sender's authentication results, so mail auto-forwarded from Gmail or Outlook still passes.
* Mail that fails authentication under a permissive policy, or arrives with no authentication, is delivered with the `unauthenticated` label.
* An API key created without read access to the hidden labels does not see those messages even with the include flags set.
* `GET https://api.agentmail.to/v0/inboxes/{inbox_id}/messages/{message_id}/attachments/{attachment_id}` returns `download_url` (the original file, no `Authorization` header needed) and, for files AgentMail can extract text from (PDFs, Word documents, spreadsheets), `text_url`.
* Attachment links expire at `expires_at`, one hour after the call. The ids stay valid, so repeat the call for fresh links.
* `PATCH https://api.agentmail.to/v0/inboxes/{inbox_id}/messages/{message_id}` takes `add_labels` and `remove_labels` and returns the message's new label state.
* Delivery takes about two seconds. If a fresh email is missing, wait a moment and list again.
* `thread_id` groups a message with its conversation. `in_reply_to` and `references` hold the ids of earlier messages in the chain.

## Not supported

* Listing does not return bodies. Only the 200-character `preview` is in a summary. Get the message by id for the body.
* The attachment endpoint returns links, not file bytes. Fetch `download_url` separately.
* `from`, `to`, and `subject` filters do not match fragments in the middle of a word.
* A `from`, `to`, or `subject` value made only of digits is read as a number and fails validation. Include a non-digit character.
* No include flag reveals virus mail or mail dropped for an enforcing DMARC failure. Those messages are never stored.
* An inbox cannot mail its own address. A test email from the inbox to itself never arrives.
* `text` and `preview` can be absent. Some clients, Gmail and Outlook forwards in particular, send HTML with no plain-text part. Read `html` in that case.
* Do not store `download_url` or `text_url`. Both stop working one hour after the call that returned them.

## Errors

| Error                  | HTTP | Cause                                                                                                                                                                                   | Fix                                                             |
| ---------------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| `Inbox not found`      | 404  | The inbox is not visible to the API key, usually a typo in `inbox_id`.                                                                                                                  | Check the `inbox_id`.                                           |
| `ValidationError`      | 400  | A malformed parameter: `limit` above 100 with a `labels`, `from`, `to`, or `subject` filter, a partial `before` or `after` timestamp, or an all-digit `from`, `to`, or `subject` value. | Correct the field named in the `errors` array.                  |
| `Message not found`    | 404  | No message with that id is visible to the key, usually the message lives in a different inbox than the one in the request.                                                              | Use the `inbox_id` and `message_id` pair from the same listing. |
| `Attachment not found` | 404  | The `attachment_id` does not belong to that message.                                                                                                                                    | Get the message and read `attachments[].attachment_id`.         |

## Verify

```bash
curl -s "https://api.agentmail.to/v0/inboxes/example@agentmail.to/messages?limit=1" \
  -H "Authorization: Bearer $AGENTMAIL_API_KEY"
```

Success is a `200` JSON body with `count` and a `messages` array. Each entry carries `message_id`, `thread_id`, `labels`, `from`, `to`, `subject`, and `preview`.

## Related

* [/core/send](/core/send) answers the mail received here, using the `message_id` from a listing.
* [/advanced/webhooks](/advanced/webhooks) notifies the moment mail arrives instead of polling.
* [/core/conversations](/core/conversations) fetches a whole conversation in one call.
* [/core/inbound-control](/core/inbound-control) sets the rules behind the `blocked` label.
