Attachments
attachments is an array of up to 50 objects. Each attachment needs a filename and exactly one of content or path.
filenamestringrequired1 to 255 characters. Shown to the recipient.
contentstringoptionalFile bytes as base64. The SDK also accepts a Buffer/Uint8Array and encodes it for you. Use this or path.
pathstringoptionalA public https URL. SpacePost downloads it when the email is sent. Use this or content.
content_typestringoptionalMIME type, such as application/pdf. Alias contentType in the SDK.
content_idstringoptionalMakes the attachment inline, referenceable as cid:<content_id> in your HTML. Alias contentId in the SDK.
Limits
Section titled “Limits”- The whole message, including all attachments after base64 encoding, must be under 40 MB. Base64 inflates files by about 33%, so keep the raw total under roughly 30 MB.
- URL attachments (
path) must usehttps, must resolve to a public address (private networks are refused), are fetched with a 10 second timeout and must not redirect. Fetch failures mark the emailfailed. - Attachment bytes are removed from storage once the email is sent; only metadata (name, type, size) is kept in the log.
- Attachments don’t cost extra on any standard plan. SpacePost is built for transactional and marketing email, not bulk file delivery: if your attachment volume is unusually high compared with your email volume, we may contact you about a custom plan (see the Acceptable Use Policy).
Attach a file by content
Section titled “Attach a file by content”import { readFile } from "node:fs/promises";import { SpacePost } from "@spacepost/node";
const spacepost = new SpacePost(process.env.SPACEPOST_API_KEY);
const { data, error } = await spacepost.emails.send({ from: "Acme <billing@acme.com>", to: "user@example.com", subject: "Invoice 1042", text: "Your invoice is attached.", attachments: [{ filename: "invoice-1042.pdf", content: await readFile("invoice-1042.pdf"), contentType: "application/pdf" }],});
console.log(error ?? data);curl -X POST https://api.spacepost.dev/emails \ -H "Authorization: Bearer $SPACEPOST_API_KEY" -H "Content-Type: application/json" \ -d "{ \"from\": \"Acme <billing@acme.com>\", \"to\": [\"user@example.com\"], \"subject\": \"Invoice 1042\", \"text\": \"Your invoice is attached.\", \"attachments\": [{ \"filename\": \"invoice-1042.pdf\", \"content_type\": \"application/pdf\", \"content\": \"$(base64 -w0 invoice-1042.pdf)\" }] }"On macOS use base64 -i invoice-1042.pdf (no -w0).
import base64
with open("invoice-1042.pdf", "rb") as f: content = base64.b64encode(f.read()).decode()
requests.post( "https://api.spacepost.dev/emails", headers={"Authorization": f"Bearer {os.environ['SPACEPOST_API_KEY']}"}, json={ "from": "Acme <billing@acme.com>", "to": ["user@example.com"], "subject": "Invoice 1042", "text": "Your invoice is attached.", "attachments": [{"filename": "invoice-1042.pdf", "content_type": "application/pdf", "content": content}], },)Attach a file by URL
Section titled “Attach a file by URL”{ "from": "Acme <billing@acme.com>", "to": ["user@example.com"], "subject": "Your report", "text": "Report attached.", "attachments": [{ "filename": "report.pdf", "path": "https://files.acme.com/reports/2026-10.pdf" }]}Inline images
Section titled “Inline images”Set a content_id and reference it with a cid: URL in your HTML.
import { readFile } from "node:fs/promises";import { SpacePost } from "@spacepost/node";
const spacepost = new SpacePost(process.env.SPACEPOST_API_KEY);
await spacepost.emails.send({ from: "Acme <hello@acme.com>", to: "user@example.com", subject: "Welcome", html: '<img src="cid:logo" alt="Acme" width="120" /><p>Welcome aboard.</p>', attachments: [{ filename: "logo.png", content: await readFile("logo.png"), contentType: "image/png", contentId: "logo" }],});Inline images are sent as part of the message and display without a network request, but many clients (Gmail on the web in particular) show them best when they are small. For large images host them on your own CDN and use a normal https:// URL.