> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bosonware.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sending emails with attachments

> Attach files from disk, generated CSVs, and HTTP uploads to BosonMail emails with automatic multipart encoding and size validation.

The BosonMail SDK automatically switches to `multipart/form-data` when you include attachments. This guide covers sending a PDF invoice from disk, generating a CSV in memory, handling multiple files, validating sizes before sending, and securely accepting uploads from HTTP requests.

## Attach a file from disk

Read a PDF with `fs/promises` and pass the resulting `Buffer` directly. Node.js `Buffer` is a `Uint8Array` subclass, so it satisfies the SDK type without conversion.

```typescript send-invoice.ts theme={null}
import { readFile } from "node:fs/promises";
import BosonMailClient from "@bosonware-ent/bosonmail";

const apiKey = process.env.BOSONMAIL_API_KEY;
if (!apiKey) throw new Error("BOSONMAIL_API_KEY is not set");

const client = new BosonMailClient({ apiKey });

async function sendInvoice(to: string, invoicePath: string) {
  const pdf = await readFile(invoicePath);

  const result = await client.sendEmail(to, "Your invoice", {
    text: "Please find your invoice attached.",
    attachments: [
      {
        filename: "invoice.pdf",
        content: pdf,
        contentType: "application/pdf",
      },
    ],
  });

  if (!result.success) {
    console.error("Failed to send invoice:", result.errorMessage);
    return;
  }

  console.log("Invoice sent. Message ID:", result.messageId);
}

sendInvoice("customer@example.com", "./invoices/INV-2026-001.pdf");
```

## Generate and attach a CSV in memory

Use `TextEncoder` to turn a CSV string into a `Uint8Array`, then attach it without writing to disk.

```typescript send-csv.ts theme={null}
import BosonMailClient from "@bosonware-ent/bosonmail";

const apiKey = process.env.BOSONMAIL_API_KEY;
if (!apiKey) throw new Error("BOSONMAIL_API_KEY is not set");

const client = new BosonMailClient({ apiKey });

function generateCsv(rows: string[][]): Uint8Array {
  const lines = rows.map((r) => r.map((c) => `"${c.replace(/"/g, '""')}"`).join(","));
  return new TextEncoder().encode(lines.join("\n"));
}

async function sendReport(to: string) {
  const csv = generateCsv([
    ["Date", "Product", "Revenue"],
    ["2026-10-01", "BosonMail Pro", "1200"],
    ["2026-10-02", "Extra pack", "150"],
  ]);

  const result = await client.sendEmail(to, "Monthly revenue report", {
    text: "Your report is attached.",
    attachments: [
      {
        filename: "report.csv",
        content: csv,
        contentType: "text/csv",
      },
    ],
  });

  console.log(result.success ? "Report sent" : "Failed: " + result.errorMessage);
}

sendReport("finance@example.com");
```

## Validate sizes before sending

The SDK enforces limits at send time, but validating early lets you fail fast and give clear feedback to users. This helper mirrors the SDK rules so your application can pre-check files.

```typescript validate-attachments.ts theme={null}
import type { SendAttachment } from "@bosonware-ent/bosonmail";

const MAX_ATTACHMENTS = 10;
const MAX_PER_FILE = 10 * 1024 * 1024; // 10 MiB
const MAX_TOTAL = 20 * 1024 * 1024; // 20 MiB

export interface ValidationResult {
  ok: boolean;
  error?: string;
}

export function validateAttachments(attachments: SendAttachment[]): ValidationResult {
  if (attachments.length > MAX_ATTACHMENTS) {
    return { ok: false, error: `A maximum of ${MAX_ATTACHMENTS} attachments is allowed` };
  }

  let total = 0;
  for (const att of attachments) {
    if (!att.filename || att.filename.trim().length === 0) {
      return { ok: false, error: "Each attachment must have a filename" };
    }

    let size: number;
    if (att.content instanceof Blob) {
      size = att.content.size;
    } else if (att.content instanceof ArrayBuffer) {
      size = att.content.byteLength;
    } else if (att.content instanceof Uint8Array) {
      size = att.content.byteLength;
    } else {
      return { ok: false, error: `Invalid content for attachment "${att.filename}"` };
    }

    if (size > MAX_PER_FILE) {
      return { ok: false, error: `Attachment "${att.filename}" exceeds the 10 MB limit` };
    }

    total += size;
  }

  if (total > MAX_TOTAL) {
    return { ok: false, error: "Total attachment size exceeds the 20 MB limit" };
  }

  return { ok: true };
}
```

## Handle HTTP file uploads server-side

If your application accepts file uploads from a browser or mobile app, read them on your server and pass the `File` or `Blob` objects to the SDK. Never expose your BosonMail API key in browser code.

```typescript handle-upload.ts theme={null}
import BosonMailClient from "@bosonware-ent/bosonmail";

const apiKey = process.env.BOSONMAIL_API_KEY;
if (!apiKey) throw new Error("BOSONMAIL_API_KEY is not set");

const client = new BosonMailClient({ apiKey });

export async function handleUpload(request: Request): Promise<Response> {
  const form = await request.formData();
  const to = form.get("to")?.toString() ?? "";
  const subject = form.get("subject")?.toString() ?? "Document submission";
  const text = form.get("text")?.toString() ?? "Please see the attached documents.";

  const files = form.getAll("files");
  const attachments = files
    .filter((f): f is File => f instanceof File)
    .map((file) => ({
      filename: file.name,
      content: file,
      contentType: file.type || "application/octet-stream",
    }));

  const result = await client.sendEmail(to, subject, { text, attachments });

  if (!result.success) {
    return new Response(JSON.stringify({ error: result.errorMessage }), {
      status: result.statusCode ?? 500,
      headers: { "Content-Type": "application/json" },
    });
  }

  return new Response(JSON.stringify({ messageId: result.messageId }), {
    status: 200,
    headers: { "Content-Type": "application/json" },
  });
}
```

## Security checklist

> [accordion] Never expose API keys in the browser
>
> The SDK requires your BosonMail API key on every request. Keep it server-side. Upload files to your own backend, validate them, then call the SDK from there.

> [accordion] Validate uploads in your app
>
> Check file types, sizes, and names before passing them to the SDK. The SDK validates content type and size too, but defense in depth protects your API quota and reduces unnecessary requests.

> [accordion] Avoid logging sensitive content
>
> Do not log attachment content, recipient addresses, or the client object (it contains the API key). Log only message IDs and send outcomes for troubleshooting.

> [accordion] Use HTTPS for uploads
>
> Ensure the endpoint that receives user uploads is served over HTTPS to prevent interception of files and metadata in transit.

## Next steps

> [card] [Multiple recipients](https://docs.bosonware.com/guides/multiple-recipients.md)
>
> Send the same attachment to many recipients with To, CC, or BCC.

> [card] [Monitoring delivery](https://docs.bosonware.com/guides/monitoring-delivery.md)
>
> Track whether an email with attachments was delivered or bounced.

> [card] [SDK reference: Attachments](https://docs.bosonware.com/sdk/attachments.md)
>
> Read the concise SDK reference for attachment types and limits.

> [card] [Production best practices](https://docs.bosonware.com/guides/production-best-practices.md)
>
> Learn how to secure keys, handle errors, and manage quotas in production.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.