Attachments & scheduling
Attach files as Buffers or base64, embed inline images with content IDs, and schedule sends with sendAt — plus which providers support each.
Attachments
Attach files with the attachments array. Each entry needs a filename and
content (a Node Buffer or a base64 string). Radon encodes it to whatever wire
format the provider wants.
import { readFile } from "node:fs/promises";
const pdf = await readFile("./invoice.pdf"); // Buffer
await email.send({
to: "ada@example.com",
subject: "Your invoice",
text: "The invoice is attached.",
attachments: [
{ filename: "invoice.pdf", content: pdf },
],
});Buffer vs. base64
content accepts either form:
// A Buffer — read from disk, generated in memory, fetched from storage.
{ filename: "report.csv", content: csvBuffer }
// A base64 string — already-encoded bytes (Radon assumes base64, not raw text).
{ filename: "logo.png", content: "iVBORw0KGgoAAAANSUhEUg..." }A string content is treated as base64
When content is a string, Radon treats it as already base64-encoded bytes —
it does not encode it for you. To attach raw text, wrap it:
content: Buffer.from("hello,world", "utf8").
Content type
contentType is the MIME type. Omit it and Radon guesses from the filename
extension, falling back to application/octet-stream.
{ filename: "data.json", content: buf } // → application/json (guessed)
{ filename: "export.bin", content: buf, contentType: "application/x-custom" } // explicitGuessed types cover the common extensions: pdf, png, jpg/jpeg, gif,
webp, svg, csv, txt, html, json, zip, doc/docx, xls/xlsx,
and ics.
Inline images
To embed an image in the HTML instead of listing it as a download, set
disposition: "inline" and a contentId, then reference it from the HTML with
cid:<id>.
await email.send({
to: "ada@example.com",
subject: "Newsletter",
html: '<img src="cid:logo" alt="Acme" /><p>This month at Acme…</p>',
attachments: [
{ filename: "logo.png", content: logoBuffer, disposition: "inline", contentId: "logo" },
],
});Attachment fields
filenamestringrequiredFile name shown to the recipient, e.g. "invoice.pdf".
contentstring | BufferrequiredFile bytes — a Buffer, or a base64-encoded string.
contentTypestringMIME type. Guessed from the filename when omitted.
contentIdstringContent-ID for inline embedding, referenced as cid:<id> in the HTML.
disposition"attachment" | "inline"default: "attachment""inline" for embedded images; "attachment" for downloads.
Provider support
Most providers support attachments. Two don't:
No attachments on Termii or Pinpoint
Termii (template/OTP email) and Amazon Pinpoint (SimpleEmail) carry no
attachments — capabilities.attachments is false. Amazon SES does support
them: Radon assembles a raw MIME message under the hood, since SES's simple
content can't carry files. Check provider.capabilities.attachments before
relying on it.
Scheduling
Pass a Date as sendAt to schedule delivery for the future. When the provider
supports it, the result returns status: "scheduled".
await email.send({
to: "ada@example.com",
subject: "Reminder",
text: "Your trial ends tomorrow.",
sendAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // in 24 hours
});Which providers schedule
Scheduling is supported on six providers:
Supports sendAt | Ignores sendAt |
|---|---|
| Resend, SendGrid, Mailgun, Brevo, MailerSend, SparkPost | Postmark, SES, Loops, Termii, Mailjet, Elastic Email, SMTP2GO, Pinpoint, ZeptoMail, SMTP |
Unsupported providers send immediately
On a provider without scheduling (capabilities.scheduling is false),
sendAt is silently ignored and the email goes out right away with a
queued / sent status. Check the capability if a future send time is
important, or route scheduled mail to a provider that supports it.
Next steps
Batch sending
Send many distinct emails in one call with sendBatch — native bulk endpoints where the provider has one, graceful sequential fallback everywhere else.
Webhooks
One verified handler for every provider's delivery events. The three signature tiers, the raw-body requirement, provider disambiguation, and the normalized event schema.