# Attachments

{/* // ATTACHMENTS */}

Attach files to a `send`, `reply`, or `forward`, then list or download attachment parts from a
received message. The key needs the `mailbox:send` permission string to send attachments and
`mailbox:read` to list or download them. These names are stable wire values.

## Sending attachments

Add an `attachments` array to a `send`, `reply`, or `forward` body. Each entry contains standard
base64 file bytes and optional `filename` and `content_type`. Extrovert builds the multipart MIME
message.

Attachments survive the [Review Loop](https://docs.extrovert.dev/review-loop/agent-contract/): a queued message keeps its files
(and its `reply_to` and custom `headers`) on the review row, and the approved send delivers them. A
redraft via `submit_revision` may replace them: supply `attachments` to swap the set, omit the field
to leave it untouched, or pass `[]` to clear it. The human reviews the message the recipient gets.

```bash frame="terminal"
curl -sS -X POST "$EXTROVERT_API_BASE_URL/v1/inboxes/agent7%40extrovertmail.com/send" \
  -H "Authorization: Bearer $EXTROVERT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["ops@acme.test"],
    "subject": "report",
    "text": "Attached.",
    "intent": { "summary": "Send ops the weekly deploy report they asked for" },
    "attachments": [
      { "filename": "report.pdf", "content_type": "application/pdf", "content_base64": "JVBERi0…" }
    ]
  }'
```

  ```ts title="send-with-attachment.ts"
import { readFileSync } from "node:fs";

await inbox.send({
  to: "ops@acme.test",
  subject: "report",
  text: "Attached.",
  intent: { summary: "Send ops the weekly deploy report they asked for" },
  attachments: [
    {
      filename: "report.pdf",
      content_type: "application/pdf",
      content_base64: readFileSync("report.pdf").toString("base64"),
    },
  ],
});
```

  ```json title="send_email (tool call)"
{
  "name": "send_email",
  "arguments": {
    "inbox": "agent7@extrovertmail.com",
    "to": ["ops@acme.test"],
    "subject": "report",
    "text": "Attached.",
    "intent": { "summary": "Send ops the weekly deploy report they asked for" },
    "attachments": [
      { "filename": "report.pdf", "content_type": "application/pdf", "content_base64": "JVBERi0…" }
    ]
  }
}
```
**Body size:** The request body is capped at about 25 MB. Base64 adds about 33% overhead, leaving roughly 18 MB for
  attachment bytes after the rest of the request.

## Listing and downloading

A received message exposes attachment metadata first. Download the bytes using the part's opaque
`id`.

| You want | Endpoint | SDK | MCP |
|---|---|---|---|
| List attachment metadata | `GET …/messages/{id}/attachments` | `inbox.attachments(msgId)` | `list_attachments` |
| Download one part's bytes | `GET …/messages/{id}/attachments/{attId}` | `inbox.attachment(msgId, attId)` | `get_attachment` |

```bash frame="terminal"
# 1. list: { items: [{ id, filename, content_type, size }], total }
curl -sS "$EXTROVERT_API_BASE_URL/v1/inboxes/agent7%40extrovertmail.com/messages/msg_8Tz/attachments" \
  -H "Authorization: Bearer $EXTROVERT_API_KEY"

# 2. download: raw bytes with Content-Type + Content-Disposition set
curl -sS "$EXTROVERT_API_BASE_URL/v1/inboxes/agent7%40extrovertmail.com/messages/msg_8Tz/attachments/att_1" \
  -H "Authorization: Bearer $EXTROVERT_API_KEY" -o invoice.pdf
```

The REST download response returns raw bytes and sets `Content-Type` and `Content-Disposition` from
the part. The MCP `get_attachment` tool returns base64 content in its structured result. Neither path
requires parsing the raw MIME message.

## Next

- [Messages, threads and search](https://docs.extrovert.dev/concepts/messages-and-threads/)
- [API: Messages and threads](https://docs.extrovert.dev/api/messages-and-threads/#attachments)
- [Webhooks and HMAC](https://docs.extrovert.dev/concepts/webhooks/)