Attachments
Attach receipts, invoices and tickets to a message - up to 10 files and 10 MB decoded, with the encoding rules that apply.
Updated
Attachments go in the JSON body, base64-encoded.
{
"from": "Acme <billing@mail.yourdomain.com>",
"to": ["customer@example.com"],
"subject": "Your invoice",
"html": "<p>Invoice attached.</p>",
"attachments": [
{
"filename": "invoice-10428.pdf",
"content_type": "application/pdf",
"content": "JVBERi0xLjQKJcfs..."
}
]
}| Field | Required | Notes |
|---|---|---|
filename | Yes | What the recipient sees. |
content | Yes | Base64. A data: URI prefix is accepted and stripped. |
content_type | No | Defaults to application/octet-stream. Set it so mail clients preview correctly. |
content_id | No | Embeds the file in the HTML instead of listing it as a download. See below. |
Inline images
A logo in the body is an attachment the HTML points at, rather than a file at
the foot of the message. Give it a content_id and reference the same value
with cid::
{
"from": "Acme <hello@yourdomain.com>",
"to": ["customer@example.com"],
"subject": "Your receipt",
"html": "<img src=\"cid:logo\" alt=\"Acme\" width=\"120\"><p>Thanks for your order.</p>",
"attachments": [
{
"filename": "logo.png",
"content": "iVBORw0KGgo...",
"content_type": "image/png",
"content_id": "logo"
}
]
}The value is yours to choose and only has to match between the two places.
Angle brackets are accepted and stripped, so logo and <logo> behave the
same - the Content-ID header carries them and the cid: URL does not, and you
should not have to remember which.
Inline images are not always shown
Most clients block remote images until the reader allows them, and some treat
inline parts the same way. Give every image an alt, and never put text a
reader needs inside one - a receipt whose total is only in a picture is a
receipt half your recipients cannot read.
Limits
- 10 files per message
- 10 MB total, measured after decoding
- No programs: files that run when opened -
.exe,.msi,.bat,.cmd,.js,.vbs,.ps1,.scr,.jar,.isoand similar - are refused. Gmail and Outlook reject them anyway. Send a download link instead.
Over either and you get a validation_error before anything is sent. Base64 is
about 33% larger than the raw bytes, so a 10 MB limit means roughly 13.3 MB of
encoded payload.
Example
import { readFile } from "node:fs/promises";
const pdf = await readFile("./invoice-10428.pdf");
await fetch("https://api.sendpository.com/v1/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SENDPOSITORY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "Acme <billing@mail.yourdomain.com>",
to: [customer.email],
subject: "Your invoice",
html: "<p>Invoice attached.</p>",
attachments: [
{
filename: "invoice-10428.pdf",
content_type: "application/pdf",
content: pdf.toString("base64"),
},
],
}),
});Attachments can't be scheduled
attachments and scheduled_at can't be combined. We hand the file bytes
straight to the provider and never store them, so there would be nothing left
to attach when the schedule fired. The request is refused rather than silently
dropping your file.
Keeping mail deliverable
Large attachments hurt delivery. For anything sizeable, link to a download in your app instead - it's faster for the recipient and better for your sending reputation.