SendpositoryDocs
Guides

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..."
    }
  ]
}
FieldRequiredNotes
filenameYesWhat the recipient sees.
contentYesBase64. A data: URI prefix is accepted and stripped.
content_typeNoDefaults to application/octet-stream. Set it so mail clients preview correctly.
content_idNoEmbeds 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, .iso and 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.

On this page