## Send an attachment

Add an `attachments` array to a send, reply, reply-all or forward. Each attachment has either `content` (base64) or `url`.

A message can carry at most 100 attachments and 10 MB of files in total (decoded), whether they come from `content` or `url`; a request body is at most 16 MB. Larger mail is refused with a `400 validation` error before anything is sent: link to big files instead. `content` must be valid base64 (standard or URL-safe alphabet, padding and line breaks optional); anything else is a `400` whose `param` names it, for example `attachments[0].content`. Filenames can use any characters, including non-Latin scripts.

| Field                | Meaning                                                                                         |
|----------------------|-------------------------------------------------------------------------------------------------|
| `filename`           | File name shown to the recipient. With `url`, defaults to the last path segment.                |
| `content_type`      | MIME type. With `url`, defaults to the response’s `Content-Type`.                               |
| `content`           | The file, base64 encoded.                                                                       |
| `url`               | A public `http` or `https` URL that MachineMail downloads when it sends.                       |
| `content_disposition`| `attachment` (default) or `inline`.                                                            |
| `content_id`        | For inline images: the ID your HTML refers to as `cid:<content_id>`.                          |

### Example Code

#### curl
```bash
curl https://api.machinemail.dev/v0/inboxes/support-agent@machinemail.dev/messages/send \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{\n    "to": ["ada@example.com"],\n    "subject": "Your report",\n    "text": "Report attached.",\n    "attachments": [\
      { "filename": "report.csv", "content_type": "text/csv", "content": "ZGF0ZSx0b3RhbAoyMDI2LTEwLTA4LDQy" },\
      { "url": "https://files.acme.dev/q3-summary.pdf" }\
    ]\n  }'
```

#### TypeScript
```typescript
import { readFile } from 'node:fs/promises';

const content = (await readFile('report.csv')).toString('base64');
await fetch('https://api.machinemail.dev/v0/inboxes/support-agent@machinemail.dev/messages/send', {
  method: 'POST',
  headers: { Authorization: `Bearer \\$MACHINEMAIL_API_KEY`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    to: ['ada@example.com'],
    subject: 'Your report',
    text: 'Report attached.',
    attachments: [{ filename: 'report.csv', content_type: 'text/csv', content }],
  }),
});
```

#### Python
```python
import base64, os, requests

with open("report.csv", "rb") as f:
    content = base64.b64encode(f.read()).decode()

requests.post(
    "https://api.machinemail.dev/v0/inboxes/support-agent@machinemail.dev/messages/send",
    headers={"Authorization": f"Bearer {os.environ['MACHINEMAIL_API_KEY']}"},
    json={
        "to": ["ada@example.com"],
        "subject": "Your report",
        "text": "Report attached.",
        "attachments": [{"filename": "report.csv", "content_type": "text/csv", "content": content}],
    },
).raise_for_status()
```

### Attachments from a URL
With `url`, MachineMail fetches the file at send time. The rules:

- Only `http` and `https` URLs that resolve to public addresses. Private, loopback and link-local addresses are refused.
- Redirects are not followed. Use the final URL; for signed storage links, sign the object URL itself.
- At most 10 MB per file (and 10 MB for all attachments together), and the download must finish within 20 seconds.
- Any failure (a non-2xx status, a timeout, a file too large) rejects the whole send with a `validation` error whose `param` names the attachment, for example `attachments[1].url`. Nothing is sent with a missing file.

### Inline images
Give the image a `content_id`, set `content_disposition` to `inline`, and refer to it from the HTML body. The `content_id` is what makes it inline: an image with `content_disposition: inline` but no `content_id` is delivered as an ordinary attachment.

### Drafts
Drafts take the same attachments as a send, base64 `content` or `url`. A `url` is fetched when the draft is sent (for a scheduled draft, at `send_at`), so its `size` is `0` until then and a URL that has stopped working makes the send fail.

### Forwarding
A REST forward does not copy the original message’s attachments. To pass them on, download them and add them to the forward’s `attachments`.

### Download
Get the attachment’s metadata and a download URL:

#### Example Code
```bash
curl https://api.machinemail.dev/v0/inboxes/support-agent@machinemail.dev/messages/msg_01m4fybrc20000ypd31v108d71/attachments/att_01m4fybrc10000qyygf5yv5thv \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

##### Response
```json
{
  "attachment_id": "att_01m4fybrc10000qyygf5yv5thv",
  "filename": "invoice.pdf",
  "size": 13,
  "content_type": "application/pdf",
  "content_disposition": "attachment",
  "download_url": "https://...",
  "expires_at": "2026-10-09T10:02:01.949Z"
}
```

### Check the risk first
Before an agent opens a file, read the matching entry in `trust.attachments`:

```json
[
  {
    "attachment_id": "att_01m4fybrc10000qyygf5yv5thv",
    "filename": "invoice.pdf",
    "content_type": "application/pdf",
    "risk": "low",
    "reasons": []
  }
]
```

### Warning
Never execute or render a `high` risk attachment, and do not hand any attachment to a tool that can run code. If an agent needs a file’s contents, prefer `extracted_text`.

### Attachments are fetched per message. 
There is no thread-level attachment download endpoint; use the message that carries the file.
