Attachments
Send an invoice, a receipt, a report, or an inline logo — attach files to any email in five steps.
Install the SDK
Attachments need cmdsend v1.2.0 or later. Node.js 18+ is required.
npm install cmdsendSet 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.
CMDSEND_API_KEY=cmd_your_api_keyimport { Cmdsend } from 'cmdsend';
export const cmdsend = new Cmdsend(process.env.CMDSEND_API_KEY);Your from domain must already be verified — see Domains & DNS.
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.
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-..." 1Or 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.
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
| Parameter | Description |
|---|---|
filenamestringrequired | Name the recipient sees. Optional only when using path, where it falls back to the filename in the URL. |
contentBuffer | Uint8Array | Blob | string | The file itself. A string is treated as already base64-encoded. Mutually exclusive with path. |
pathstring | Public https URL cmdsend downloads the file from. Mutually exclusive with content. |
content_typestring | MIME type. Inferred from the filename when omitted. |
content_idstring | 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.
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 -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 message | 20 |
| Total size | 40 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.
| Error | Cause |
|---|---|
InvalidAttachment400 | Blocked file type, empty file, invalid base64, over the total size budget, or a path that could not be downloaded. |
InvalidJSON400 | The body is not valid JSON — usually raw file bytes sent where a base64 string was expected. |
Validation Error400 | More than 20 attachments, or an entry with both content and path (or neither). |
PayloadTooLarge413 | 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.