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

FieldTypeRequiredDescription
contentstring or number[]One of content or pathThe file as a base64 string, or as an array of byte values.
pathstringOne of content or pathA public URL to download the file from.
filenamestringNoName 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_typestringNoMIME type, such as application/pdf.
content_idstringNoMakes 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 with path.
  • Files passed by path are 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/batch does not accept attachments.

Send a file inline

TypeScript
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:

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.

Shell
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:

JSON
{
  "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.