Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,27 @@ description: "New features, improvements, and fixes across the Sendmux platform.
rss: true
---

<Update label="7 July 2026" tags={["Improvements"]} rss={{ title: "Attachments are easier for agents to move and read" }}>
## Sending API

Agents can now upload Sending API attachment bytes first and send by `attachment_id`, keeping normal files out of prompts and JSON base64 payloads.

Hosted agents can mint a short-lived upload URL, upload with the returned headers and no Sendmux API key in the upload command, then attach the returned `attachment_id` to single or batch sends.

## Mailbox API

Mailbox attachment download links are now shorter and use stable attachment refs, making them easier to pass through agent tools and command-line workflows.

Existing short-lived download links keep the same expiry and safety model, while fresh message and attachment reads return the new compact link shape.

## Developer tools

MCP clients can now read text attachments directly through `mailbox_read_attachment`, so agents no longer need a separate web fetch step for common files like Markdown recipes.

Hosted MCP Sending tools now support direct attachment uploads and delegated upload URLs, so agents do not need to handle API keys or base64 payloads.

</Update>

<Update label="6 July 2026" tags={["Improvements"]} rss={{ title: "Domain bounce handling is clearer" }}>
## Domain verification

Expand Down
86 changes: 74 additions & 12 deletions guides/attachments.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,91 @@ description: "Send, upload, and download attachments with Sendmux."
keywords: ["attachments", "file uploads", "file downloads", "base64", "MIME types", "size limits"]
---

HTTP send requests can include up to 10 attachments. Each attachment is sent as base64 content inside the JSON body.
For the Sending API, upload file bytes first and send by `attachment_id`. Inline base64 attachments remain supported for small generated content and older integrations, but uploaded refs are the preferred path for normal files.

## Limits

| Limit | Value |
| --- | --- |
| Attachments per email | 10 |
| Request body | 25 MB |
| Sending API binary upload | 18 MiB |
| Final sent message | 25 MB |
| Mailbox upload attachment | 7,500,000 bytes |

Base64 content counts towards the request body size, so keep files below the full request limit.
Base64 content counts towards the request body size and increases payload size. Upload refs avoid that overhead, but the final encoded email must still fit within the 25 MB message limit.

Mailbox attachment uploads are available for mailbox send workflows that need to upload a file first and then reference the returned blob ID when sending. Direct mailbox uploads and presigned mailbox uploads use the same per-attachment cap.

## Upload before sending
## Upload before sending with the Sending API

For local tools and SDK helpers, send the file path and let the tool read the bytes locally. For shell-capable or hosted agents, mint a short-lived presigned upload URL, then upload the file bytes with the exact returned method, headers, content type, and content length.
Upload bytes directly when your client has the API key:

```bash
SIZE_BYTES="$(wc -c < invoice.pdf | tr -d ' ')"
ATTACHMENT_ID="$(
curl -sS -X POST "https://smtp.sendmux.ai/api/v1/emails/attachments?filename=invoice.pdf" \
-H "Authorization: Bearer smx_mbx_your_key_here" \
-H "Content-Type: application/pdf" \
-H "Content-Length: $SIZE_BYTES" \
--data-binary @invoice.pdf \
| jq -r '.data.attachment_id'
)"
```

Then reference it from `POST /emails/send` or `POST /emails/send/batch`:

```json
{
"from": { "email": "hello@example.com" },
"to": [{ "email": "user@example.com" }],
"subject": "Invoice attached",
"html_body": "<p>Please find your invoice attached.</p>",
"attachments": [{ "attachment_id": "att_tz4a98xxat96iws9zmbrgj3a" }]
}
```

## Delegated uploads

The presigned upload URL does not include an API key. It can write only the single attachment it was minted for, expires quickly, and returns the same blob ID shape as a direct mailbox attachment upload.
Use a delegated upload when a browser, hosted agent, or another remote client needs to upload bytes without seeing your API key.

```bash
SIZE_BYTES="$(wc -c < invoice.pdf | tr -d ' ')"
SHA256="$(shasum -a 256 invoice.pdf | awk '{print $1}')"

curl -X POST https://smtp.sendmux.ai/api/v1/emails/attachment-uploads \
-H "Authorization: Bearer smx_mbx_your_key_here" \
-H "Content-Type: application/json" \
-d "{
\"filename\": \"invoice.pdf\",
\"content_type\": \"application/pdf\",
\"size_bytes\": $SIZE_BYTES,
\"sha256\": \"$SHA256\"
}"
```

The response includes an `upload_url` and required `headers`. Send a `PUT` request to that URL with the exact file bytes and returned headers. The upload token can write only that attachment, expires quickly, and does not grant send access.

## Agent and MCP workflows

For local MCP clients, use `sending_upload_attachment` with `file_path` before `sending_send_email` or `sending_send_email_batch`. The tool reads the local file from a client-approved root and returns an `attachment_id`.

For hosted MCP clients or shell-capable agents, use `sending_create_attachment_upload`, upload the file bytes to the returned `upload_url` with the returned headers, then pass the resulting `attachment_id` in `sending_send_email` or `sending_send_email_batch`. Do not place file bytes or large base64 strings in the prompt.

For mailbox sends, use `mailbox_upload_attachment` first and send with the returned `blob_id`. For inbound mailbox attachments, use `mailbox_read_attachment` first when you need text-like content.

## Mailbox upload before sending

Mailbox send workflows can also upload a file first and reference the returned blob ID when sending. Direct mailbox uploads and presigned mailbox uploads use the same per-attachment cap.

For local tools and SDK helpers, send the file path and let the tool read the bytes locally. For shell-capable or hosted agents, mint a short-lived presigned upload URL, then upload the file bytes with the exact returned method, headers, content type, and content length.

## Allowed files

Sendmux accepts common image, document, spreadsheet, presentation, text, calendar, and contact formats. Executables and archives are rejected.

Binary files are checked against their claimed file type. Text-based files are checked by extension.

## Add an attachment
## Inline base64 compatibility

```json
{
Expand All @@ -48,10 +106,13 @@ Binary files are checked against their claimed file type. Text-based files are c
}
```

Use this form only for small content that is already in memory. For files on disk, upload bytes first and use `attachment_id`.

## Fields

| Field | Required | Notes |
| --- | --- | --- |
| `attachment_id` | Yes, for uploaded refs | Temporary ID returned by a Sending API upload endpoint. |
| `filename` | Yes | Must include an allowed extension. |
| `content` | Yes | Base64-encoded file content. |
| `type` | No | Optional MIME type override. |
Expand All @@ -74,7 +135,7 @@ Mailbox messages include attachment metadata when attachments are present:

The `download_url` is short-lived and grants access only to that attachment. Fetch it promptly with any HTTP client; no `Authorization` header is needed for the URL itself.

If the URL expires, re-read the message with `GET /mailbox/messages/{message_id}`, list or search messages again, or call the MCP `mailbox_get_attachment` tool to get a fresh `download_url`. The authenticated attachment download endpoint continues to work for clients that send bearer credentials.
If the URL expires, re-read the message with `GET /mailbox/messages/{message_id}`, list or search messages again, or call the MCP `mailbox_get_attachment` tool to get a fresh `download_url`. MCP clients that need attachment text should call `mailbox_read_attachment` first; it returns inline text for text-like attachments and returns a resource link for binary or oversized files. The authenticated attachment download endpoint continues to work for clients that send bearer credentials.

Realtime received-message events can include the same attachment metadata when it is available. Handle older events where `attachments` is omitted, then re-read message metadata before downloading.

Expand All @@ -84,16 +145,17 @@ Range requests are still supported on attachment downloads, including short-live

1. Wait for or list messages.
2. Read the message metadata and find the attachment.
3. Fetch `download_url` promptly.
4. If the URL expired, re-read the message or attachment metadata and fetch the new URL.
3. For MCP, call `mailbox_read_attachment` with the message ID and attachment ID.
4. Use returned inline text directly, or fetch the returned link promptly for binary or oversized files.
5. If the URL expired, re-read the message or attachment metadata and fetch the new URL.

MCP clients should use the attachment metadata tool rather than trying to return raw binary content through the tool call.
Do not construct attachment URLs manually.

## Related guides

<Columns cols={2}>
<Card title="Send by HTTP" icon="code" href="/guides/sending-via-http">
Add attachments to single or batch send requests.
Send uploaded attachment refs in single or batch requests.
</Card>
<Card title="Mailbox push delivery" icon="radio" href="/guides/mailbox-push-delivery">
Receive live mailbox events and then download attachments from message metadata.
Expand Down
4 changes: 2 additions & 2 deletions guides/sending-via-http.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ curl -X POST https://smtp.sendmux.ai/api/v1/emails/send \
}'
```

A successful request returns `201 Created` with a queued `message_id`.
A successful request returns `200 OK` with a queued `message_id`.

## Send a batch

Expand Down Expand Up @@ -74,7 +74,7 @@ curl -X POST https://smtp.sendmux.ai/api/v1/emails/send/batch \
| `reply_to` | Direct replies to a different address. |
| `return_path` | Set the envelope sender for bounce handling. |
| `custom_headers` | Add custom `X-*` headers to the outgoing message. |
| `attachments` | Attach files as base64 content. |
| `attachments` | Attach uploaded `attachment_id` refs, or tiny inline base64 content. |
| `Idempotency-Key` | Safely retry a request without sending twice. |

See the [Sending API reference](/sending-api/introduction) for the full schema.
Expand Down
10 changes: 8 additions & 2 deletions mailbox-api/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ curl https://app.sendmux.ai/api/v1/mailbox/messages:batch-get \
"size_bytes": 12345,
"disposition": "attachment",
"content_id": null,
"download_url": "/api/v1/mailbox/messages/msg_123/attachments/att_123"
"download_url": "/api/v1/mailbox/messages/msg_123/attachment-downloads/attref_abc123?download_token=..."
}
]
}
Expand Down Expand Up @@ -731,7 +731,7 @@ curl "https://app.sendmux.ai/api/v1/mailbox/messages/msg_123/content?strip_signa
"size_bytes": 12345,
"disposition": "attachment",
"content_id": null,
"download_url": "/api/v1/mailbox/messages/msg_123/attachments/att_123"
"download_url": "/api/v1/mailbox/messages/msg_123/attachment-downloads/attref_abc123?download_token=..."
}
]
},
Expand Down Expand Up @@ -814,6 +814,12 @@ curl -X POST https://app.sendmux.ai/api/v1/mailbox/messages/send \
Small inline base64 attachments are still supported on `messages/send`, but the
upload endpoint avoids large JSON request bodies.

Message metadata can include a short-lived `download_url` for clients that need
a no-header download link. Fetch it promptly without an `Authorization` header.
If it expires, re-read message or attachment metadata to receive a fresh URL.
MCP clients should prefer `mailbox_read_attachment` for text-like inbound
attachments and use the returned link only for binary or oversized files.

Attachment downloads support byte ranges:

```bash
Expand Down
2 changes: 1 addition & 1 deletion openapi-app.json
Original file line number Diff line number Diff line change
Expand Up @@ -1173,7 +1173,7 @@
},
"download_url": {
"description": "Short-lived URL for this exact attachment. Fetch it promptly; if it expires, call the message or attachment metadata endpoint again to receive a fresh URL.",
"example": "https://app.sendmux.ai/api/v1/mailbox/messages/msg_123/attachments/blob_123?download_token=...",
"example": "https://app.sendmux.ai/api/v1/mailbox/messages/msg_123/attachment-downloads/attref_abc123?download_token=...",
"type": "string"
},
"filename": {
Expand Down
Loading