Skip to documentation
Dashboard
Guides

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.

Direct and mixed request
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();
202 Accepted
{  "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.

Prepare upload
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();
201 Created
{  "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.

Upload file
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.

Complete upload
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();
200 Response
{  "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.
On this page