Sending
Attachments
Attach files to an email as base64 content or by URL, with the 4 MB inline limit, the 50 file maximum and inline images.
Add an attachments array to POST /emails to include files. Each attachment is either inline content that you send in the request, or a URL that Sooner downloads when the email is sent.
Attachment fields
| Field | Type | Required | Description |
|---|---|---|---|
content | string or number[] | One of content or path | The file as a base64 string, or as an array of byte values. |
path | string | One of content or path | A public URL to download the file from. |
filename | string | No | Name shown to the recipient, up to 255 characters. Defaults to the last part of the URL path for path, and to file for content. |
content_type | string | No | MIME type, such as application/pdf. |
content_id | string | No | Makes the attachment an inline image you can reference as cid:<content_id> in your HTML. |
If you provide both content and path, content is used. An attachment with neither is rejected with validation_error.
Limits
- Up to 50 attachments per email.
- Inline content (
content) is limited to 4 MB per request, measured after base64 decoding and summed across all attachments in the email. Larger files must be passed by URL withpath. - Files passed by
pathare not counted against the 4 MB limit. Sooner downloads each URL when the email is sent, with a 15 second timeout. The URL must be reachable from the public internet, and a failed download is retried. POST /emails/batchdoes not accept attachments.
Send a file inline
import { readFile } from "node:fs/promises";
const pdf = await readFile("./invoice-1042.pdf");
const res = await fetch("https://api.sooner.dev/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SOONER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "Acme <billing@mail.yourcompany.com>",
to: ["ada@example.com"],
subject: "Invoice 1042",
html: "<p>Your invoice is attached.</p>",
attachments: [
{
filename: "invoice-1042.pdf",
content: pdf.toString("base64"),
content_type: "application/pdf",
},
],
}),
});
console.log(await res.json());The same with Python:
import base64
import os
import requests
with open("invoice-1042.pdf", "rb") as f:
content = base64.b64encode(f.read()).decode()
res = requests.post(
"https://api.sooner.dev/emails",
headers={"Authorization": f"Bearer {os.environ['SOONER_API_KEY']}"},
json={
"from": "Acme <billing@mail.yourcompany.com>",
"to": ["ada@example.com"],
"subject": "Invoice 1042",
"html": "<p>Your invoice is attached.</p>",
"attachments": [
{"filename": "invoice-1042.pdf", "content": content, "content_type": "application/pdf"}
],
},
timeout=30,
)
print(res.json())Send a file by URL
Use path for large files, or for files that already live in your storage.
curl -X POST https://api.sooner.dev/emails \
-H "Authorization: Bearer $SOONER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme <billing@mail.yourcompany.com>",
"to": ["ada@example.com"],
"subject": "Your report",
"html": "<p>Your report is attached.</p>",
"attachments": [
{ "filename": "report.pdf", "path": "https://files.example.com/reports/2026-11.pdf" }
]
}'Inline images
Give an attachment a content_id and reference it from your HTML:
{
"html": "<p>Our logo:</p><img src=\"cid:logo\" alt=\"Acme\" />",
"attachments": [
{ "filename": "logo.png", "content": "<base64 data>", "content_type": "image/png", "content_id": "logo" }
]
}Errors
Exceeding the inline limit returns 422 validation_error with the message "Inline attachments exceed 4 MB. Pass larger files by URL with path."
Inline attachment files are stored with the email and removed when the email leaves your log retention window.