Attachments

Send an invoice, a receipt, a report, or an inline logo — attach files to any email in five steps.

1

Install the SDK

Attachments need cmdsend v1.2.0 or later. Node.js 18+ is required.

Terminal
npm install cmdsend
2

Set your API key

Create a key with the emails:send permission in Dashboard → API Keys, then put it in your environment. Never ship it to the browser.

.env.local
CMDSEND_API_KEY=cmd_your_api_key
lib/cmdsend.js
import { Cmdsend } from 'cmdsend';

export const cmdsend = new Cmdsend(process.env.CMDSEND_API_KEY);

Your from domain must already be verified — see Domains & DNS.

3

Attach a file from disk

Pass the file as a Buffer. The SDK base64-encodes it for you, and the content type is inferred from the filename.

send-invoice.js
import { readFile } from 'node:fs/promises';
import { cmdsend } from './lib/cmdsend.js';

const result = await cmdsend.emails.send({
  from: 'Acme <billing@yourdomain.com>',
  to: 'user@example.com',
  subject: 'Your invoice is ready',
  html: '<p>Your March invoice is attached.</p>',
  attachments: [
    {
      filename: 'invoice.pdf',
      content: await readFile('./invoice.pdf'),
    },
  ],
});

console.log(result.id, result.attachments); // → "a1b2c3d4-..." 1
4

Or attach a hosted file by URL

Give a public https URL instead of file bytes and cmdsend downloads it at send time — useful when the file already lives in S3 or a CDN.

await cmdsend.emails.send({
  from: 'Acme <billing@yourdomain.com>',
  to: 'user@example.com',
  subject: 'Your invoice is ready',
  html: '<p>Your March invoice is attached.</p>',
  attachments: [
    { filename: 'invoice.pdf', path: 'https://files.yourdomain.com/invoices/123.pdf' },
  ],
});

The URL must be publicly reachable over https. Private and internal addresses are refused.

5

Confirm it went out

The send is accepted with 202 queued, scanned, then delivered. Read back the status and what was attached at any time.

const email = await cmdsend.emails.get(result.id);

console.log(email.status);      // "delivered"
console.log(email.attachments);
// [{ filename: 'invoice.pdf', content_type: 'application/pdf', size_bytes: 24518, content_purged_at: null }]

Attachment fields

ParameterDescription
filename
stringrequired
Name the recipient sees. Optional only when using path, where it falls back to the filename in the URL.
content
Buffer | Uint8Array | Blob | string
The file itself. A string is treated as already base64-encoded. Mutually exclusive with path.
path
string
Public https URL cmdsend downloads the file from. Mutually exclusive with content.
content_type
string
MIME type. Inferred from the filename when omitted.
content_id
string
Embeds the file inline so you can reference it as <img src="cid:...">.

Attach a user upload (Next.js)

A File from a form submission can be attached directly — no manual encoding. Keep the send in a Route Handler or Server Action so your key stays server-side.

app/api/send-attachment/route.ts
import { Cmdsend } from 'cmdsend';
import { NextResponse } from 'next/server';

const cmdsend = new Cmdsend(process.env.CMDSEND_API_KEY!);

export async function POST(req: Request) {
  const form = await req.formData();
  const file = form.get('file') as File;

  const result = await cmdsend.emails.send({
    from: 'Acme <hello@yourdomain.com>',
    to: String(form.get('email')),
    subject: 'Your document',
    html: '<p>The document you requested is attached.</p>',
    attachments: [{ filename: file.name, content: file }],
  });

  return NextResponse.json({ id: result.id });
}

Embed an inline image

Set content_id and reference it from your HTML with cid:. The image renders in the body instead of appearing as a download.

await cmdsend.emails.send({
  from: 'Acme <hello@yourdomain.com>',
  to: 'user@example.com',
  subject: 'Welcome to Acme',
  html: '<img src="cid:logo@acme" alt="Acme" width="120"><p>Glad you are here.</p>',
  attachments: [
    {
      filename: 'logo.png',
      content: await readFile('./logo.png'),
      content_id: 'logo@acme',
    },
  ],
});

Raw API call

Without the SDK, encode the file yourself and send it as base64 in the JSON body.

cURL
curl -X POST https://api.cmdsend.com/v1/emails/send \
  -H "Authorization: Bearer $CMDSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"from\": \"billing@yourdomain.com\",
    \"to\": \"user@example.com\",
    \"subject\": \"Your invoice\",
    \"html\": \"<p>Invoice attached.</p>\",
    \"attachments\": [
      { \"filename\": \"invoice.pdf\", \"content\": \"$(base64 -i invoice.pdf)\" }
    ]
  }"

Limits

Attachments per message20
Total size40 MB per message, after base64 encoding
Blocked file types.exe, .bat, .cmd, .com, .scr, .msi, .dll, .jar, .vbs, .js, .hta, .lnk, .reg, .ps1

Executable file types are rejected because receiving mail servers drop them anyway. Zip the file, or send a download link instead.

Scanning

Every attachment is virus- and content-scanned before the message is released to the mail servers. If a file fails the scan the whole send fails — the email's status becomes failed with scan_status: rejected, and the reason is on the email in your logs.

Retention

Attachment files are kept for 30 days after a send, then released. The record itself is permanent — filename, type and size stay on the email forever, so your history never loses what was sent. A released file is marked with content_purged_at on the attachment.

cmdsend is not a file host: keep your own copy of anything you need to re-send or serve later.

Errors

Attachments are validated before anything is queued, so a rejected request sends no email at all — nothing partial, nothing to clean up. Every failure returns { error, message }, and the message names the offending file.

ErrorCause
InvalidAttachment
400
Blocked file type, empty file, invalid base64, over the total size budget, or a path that could not be downloaded.
InvalidJSON
400
The body is not valid JSON — usually raw file bytes sent where a base64 string was expected.
Validation Error
400
More than 20 attachments, or an entry with both content and path (or neither).
PayloadTooLarge
413
The whole request body exceeded the size cap. Send fewer or smaller files, or use path.
{
  "error": "InvalidAttachment",
  "message": "Attachment \"setup.exe\" has a blocked file type. Executable attachments are rejected by receiving mail servers — send a link, or zip the file."
}

Full list in the error reference.