> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dairo.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Messages & threads

> Read incoming email as structured messages, follow conversations as threads, and reply from the same address.

Every email that reaches one of your inboxes becomes a structured message — parsed sender, subject, both bodies, attachment metadata — with no raw MIME to handle. A message that draws replies becomes a thread, so the whole conversation stays in one place.

You can work with inbound mail two ways: subscribe to the `message.received` [webhook](/webhooks/webhooks) and act the moment mail arrives, or pull messages on demand with the endpoints on this page. Every read uses the `messages:read` scope.

## List messages

`GET /v1/messages` returns messages newest-first — across every inbox, or filtered to one.

<CodeGroup>
  ```bash title="cURL" theme={null}
  curl "https://api.dairo.app/v1/messages?inboxId=7c9e6679-7425-40de-944b-e07fc1f90ae7&direction=inbound" \
    -H "Authorization: Bearer $DAIRO_API_KEY"
  ```

  ```ts title="TypeScript" theme={null}
  const page = await dairo.messages.list({
    inboxId: "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    direction: "inbound",
  });
  for (const m of page.data) console.log(m.from.address, m.subject, m.receivedAt);
  ```

  ```python title="Python" theme={null}
  page = dairo.messages.list(
      inbox_id="7c9e6679-7425-40de-944b-e07fc1f90ae7",
      direction="inbound",
  )
  for m in page.data:
      print(m.from_.address, m.subject, m.received_at)
  ```

  ```bash title="CLI" theme={null}
  dairo messages list --inbox-id 7c9e6679-7425-40de-944b-e07fc1f90ae7 --direction inbound
  ```
</CodeGroup>

```json theme={null}
{
  "object": "list",
  "data": [
    {
      "object": "message",
      "id": "8a2fdc3b-51e2-4f6a-9c11-2b7e4d9a6f3c",
      "channel": "email",
      "inboxId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "threadId": "5f2d8c1a-7b3e-4a90-8d2c-6e1f4b7a9c05",
      "direction": "inbound",
      "status": "received",
      "from": { "address": "customer@example.com", "name": "Ada Lang" },
      "to": ["support@yourapp.com"],
      "subject": "Re: Your July invoice",
      "textPreview": "Thanks — one question about the line items…",
      "hasHtml": true,
      "hasAttachments": false,
      "receivedAt": "2026-07-10T14:32:07+00:00"
    }
  ],
  "pagination": { "nextCursor": null }
}
```

| Parameter   | What it does                                                                                                         |
| ----------- | -------------------------------------------------------------------------------------------------------------------- |
| `inboxId`   | Only messages on one inbox.                                                                                          |
| `threadId`  | Only messages in one thread.                                                                                         |
| `direction` | `inbound` or `outbound`.                                                                                             |
| `channel`   | Only one [channel](/concepts/channels): `email`, `a2a`, `telegram`, or `slack`.                                      |
| `limit`     | Page size, 1–100. Defaults to 25.                                                                                    |
| `cursor`    | The `pagination.nextCursor` from the previous page — the pattern is on [Paging through lists](/concepts/pagination). |

<Note>
  `direction=outbound` with no `channel` filter returns the outbound send ledger — per-recipient delivery status, bounces, and complaints — described in [Outbound tracking](/sending/outbound-tracking). That view honors `inboxId` and `limit` but not `cursor` or `threadId`; add an explicit channel, e.g. `channel=email&direction=outbound`, to page the message shape shown here.
</Note>

## Fetch one message

List rows carry a `textPreview`. Fetch a single message for the full record — both bodies, parsed addresses, and attachment metadata.

<CodeGroup>
  ```bash title="cURL" theme={null}
  curl https://api.dairo.app/v1/messages/8a2fdc3b-51e2-4f6a-9c11-2b7e4d9a6f3c \
    -H "Authorization: Bearer $DAIRO_API_KEY"
  ```

  ```ts title="TypeScript" theme={null}
  const message = await dairo.messages.get("8a2fdc3b-51e2-4f6a-9c11-2b7e4d9a6f3c");
  console.log(message.subject, message.textBody);
  ```

  ```python title="Python" theme={null}
  message = dairo.messages.get("8a2fdc3b-51e2-4f6a-9c11-2b7e4d9a6f3c")
  print(message.subject, message.text_body)
  ```

  ```bash title="CLI" theme={null}
  dairo messages get 8a2fdc3b-51e2-4f6a-9c11-2b7e4d9a6f3c
  ```
</CodeGroup>

### Message fields

| Field                             | What it is                                                                                                        |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `id`                              | Stable message identifier.                                                                                        |
| `channel`                         | How the message traveled — `email` for mail, `a2a` for agent-to-agent delivery.                                   |
| `inboxId` / `threadId`            | The inbox that handled the message, and the conversation it belongs to.                                           |
| `direction`                       | `inbound` or `outbound`.                                                                                          |
| `status`                          | `received` on inbound mail; `sent` on your outbound copies.                                                       |
| `from`                            | Parsed sender: `{ "address", "name" }`.                                                                           |
| `to` / `cc` / `bcc`               | Recipient addresses.                                                                                              |
| `subject`                         | The subject line.                                                                                                 |
| `textPreview`                     | The first 160 characters of the text body — enough to scan a list.                                                |
| `hasHtml` / `hasAttachments`      | Whether the full record carries an HTML body or attachments.                                                      |
| `textBody` / `htmlBody`           | The full bodies, returned when you fetch one message.                                                             |
| `attachments`                     | Attachment metadata: `id`, `filename`, `contentType`, `sizeBytes`.                                                |
| `structured` / `extractionStatus` | Typed extraction results on inboxes with a schema — see [Extract structured data](/receiving/structured-inboxes). |
| `channelMetadata`                 | Channel-specific metadata; its fields depend on the channel.                                                      |
| `receivedAt` / `createdAt`        | Timestamps.                                                                                                       |

## Follow a conversation with threads

A thread is one conversation on an inbox: a message and the replies it collected. `GET /v1/threads` lists threads by most recent activity; `GET /v1/threads/{threadId}` returns one thread with its messages oldest-first, up to 100.

<CodeGroup>
  ```bash title="cURL" theme={null}
  curl "https://api.dairo.app/v1/threads?inboxId=7c9e6679-7425-40de-944b-e07fc1f90ae7" \
    -H "Authorization: Bearer $DAIRO_API_KEY"

  curl https://api.dairo.app/v1/threads/5f2d8c1a-7b3e-4a90-8d2c-6e1f4b7a9c05 \
    -H "Authorization: Bearer $DAIRO_API_KEY"
  ```

  ```ts title="TypeScript" theme={null}
  const threads = await dairo.threads.list({ inboxId: "7c9e6679-7425-40de-944b-e07fc1f90ae7" });
  const thread = await dairo.threads.get(threads.data[0].id);
  for (const m of thread.messages) console.log(m.direction, m.subject);
  ```

  ```python title="Python" theme={null}
  threads = dairo.threads.list(inbox_id="7c9e6679-7425-40de-944b-e07fc1f90ae7")
  conversation = dairo.threads.get(threads.data[0].id)
  for m in conversation.messages:
      print(m.direction, m.subject)
  ```

  ```bash title="CLI" theme={null}
  dairo threads list --inbox-id 7c9e6679-7425-40de-944b-e07fc1f90ae7
  dairo threads get 5f2d8c1a-7b3e-4a90-8d2c-6e1f4b7a9c05
  ```
</CodeGroup>

```json theme={null}
{
  "object": "thread",
  "id": "5f2d8c1a-7b3e-4a90-8d2c-6e1f4b7a9c05",
  "inboxId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "channel": "email",
  "subject": "Your July invoice",
  "messageCount": 2,
  "lastMessageAt": "2026-07-10T14:32:07+00:00",
  "lastMessagePreview": "Thanks — one question about the line items…",
  "messages": [
    { "object": "message", "id": "0c47a9e2-3d5b-4f81-8e6a-1b9d2c4f7a30", "direction": "outbound", "subject": "Your July invoice" },
    { "object": "message", "id": "8a2fdc3b-51e2-4f6a-9c11-2b7e4d9a6f3c", "direction": "inbound", "subject": "Re: Your July invoice" }
  ]
}
```

## Reply in a thread

When you email someone and they answer, the reply lands in the thread of the message it answers — Dairo connects it through standard email reply headers. To continue the exchange, [send](/sending/sending-email) from the same inbox, address the sender, and keep the subject with a `Re:` prefix so the recipient's mail client keeps the conversation together.

```ts theme={null}
await dairo.messages.send({
  inboxId: "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  to: "customer@example.com",
  subject: "Re: Your July invoice",
  text: "Good catch — the second line item is the annual discount.",
});
```

## Handle attachments

Files on inbound mail arrive as metadata only; when you need one, you mint a short-lived download link or fetch the bytes. Inbound files come from arbitrary senders, so treat them as untrusted — the endpoints and the handling rules live on [Attachments](/receiving/attachments).

## Delete messages

`POST /v1/messages/batch-delete` removes up to 1,000 messages in one call and needs the `messages:read` scope. The response reports per-id results — an id you don't own is returned under `failed` and never aborts the rest of the batch.

```json theme={null}
{
  "object": "batch_delete_result",
  "deleted": ["8a2fdc3b-51e2-4f6a-9c11-2b7e4d9a6f3c"],
  "failed": [{ "id": "00000000-0000-0000-0000-000000000000", "error": "not found" }]
}
```

## Next steps

* [Wait for one-time codes](/receiving/verification-waits) — resolve a sign-up or reset flow the moment its email arrives.
* [Agent-to-agent messages](/receiving/agent-messages) — the `a2a` channel between Dairo accounts, same message shape.
* [Build a support inbox agent](/examples/support-inbox-agent) — a full receive, process, and reply flow.
