Send an attachment
Embed a small file directly or upload a reusable private transactional attachment.
On this page
Choose direct or uploaded
Direct content avoids a separate upload request for files up to 5 MB. RouteKite retains the decoded bytes privately only long enough for queued delivery and safe retries. Uploaded attachment IDs remain useful for larger or reusable files.
- Direct and uploaded attachments can be mixed in one ordered array.
- Direct files do not appear in Assets.
- The combined decoded size cannot exceed 10 MB.
Send a direct attachment
Set filename, content_type, and standard Base64 content inside the email request. Do not send a data URL.
const response = await fetch("https://routekite.com/api/v1/emails", {method: "POST",headers: {Authorization: `Bearer ${process.env.ROUTEKITE_API_KEY}`,"Idempotency-Key": "order-123-confirmation","Content-Type": "application/json",},body: JSON.stringify({from: "updates@example.com",to: "customer@example.net",reply_to: "support@example.com",subject: "Your order is confirmed",preview_text: "Your receipt and delivery details are inside.",html: "<h1>Thank you</h1><p>Your order is confirmed.</p>",attachments: [{filename: "notes.txt",content_type: "text/plain",content: Buffer.from("Hello from RouteKite.").toString("base64"),},"c6ea638c-28a4-4b0c-a39d-74f1d29e1e9f",],}),});const email = await response.json();
{"id": "7dd5da7f-40b0-4c29-bb78-7e0d95ef9eb7","object": "email","status": "queued","created_at": "2026-08-27T10:30:00.000Z"}
1. Prepare an uploaded attachment
Calculate the exact byte size and lowercase SHA-256 digest. RouteKite reserves storage and returns an opaque upload instruction.
const response = await fetch("https://routekite.com/api/v1/attachments", {method: "POST",headers: {Authorization: `Bearer ${process.env.ROUTEKITE_API_KEY}`,"Content-Type": "application/json",},body: JSON.stringify({filename: "invoice.pdf",content_type: "application/pdf",size_bytes: 245800,sha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",retention: "auto_delete",}),});const attachment = await response.json();
{"id": "c6ea638c-28a4-4b0c-a39d-74f1d29e1e9f","object": "attachment","status": "pending","retention": "auto_delete","upload": {"method": "PUT","url": "UPLOAD_URL_FROM_RESPONSE","headers": { "Content-Type": "application/pdf" },"expires_at": "2026-08-27T12:30:00.000Z"}}
2. Upload the exact bytes
Use the exact method, URL, and headers from the prepare response. Do not send your RouteKite key to the upload destination.
import { readFile } from "node:fs/promises";const bytes = await readFile("./invoice.pdf");await fetch(attachment.upload.url, {method: attachment.upload.method,headers: attachment.upload.headers,body: bytes,});
3. Complete validation
Completion checks the object, size, content type, digest, extension, and signature. Only ready attachments can be sent.
const response = await fetch("https://routekite.com/api/v1/attachments/c6ea638c-28a4-4b0c-a39d-74f1d29e1e9f/complete",{ method: "POST", headers: { Authorization: `Bearer ${process.env.ROUTEKITE_API_KEY}` } },);const attachment = await response.json();
{"id": "c6ea638c-28a4-4b0c-a39d-74f1d29e1e9f","object": "attachment","filename": "invoice.pdf","content_type": "application/pdf","size_bytes": 245800,"status": "ready","retention": "auto_delete","expires_at": "2026-09-27T10:30:00.000Z"}
4. Reference the uploaded attachment
Add the ready ID to the attachments array. IDs must be unique, unexpired, and in the key's workspace.
- Maximum 10 files per email.
- Maximum 10,000,000 combined decoded bytes.
- auto_delete removes an uploaded file 31 days after completion.
- keep retains an uploaded file until explicit deletion.