---
title: Email attachments
description: Handle different types of email attachments including PDFs, inline images, and file uploads with validation and encoding.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/email-service/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Email attachments

Send emails with various types of attachments including PDFs, images, and file uploads with proper validation.

This example demonstrates how to send emails with various types of attachments including PDFs, inline images, and file uploads.

Configure the email binding in your Wrangler configuration file:

* [  wrangler.jsonc ](#tab-panel-8350)
* [  wrangler.toml ](#tab-panel-8351)

JSONC

```

{

  "send_email": [{ "name": "EMAIL" }],

  "vars": {

    "DOMAIN": "yourdomain.com",

  },

}


```

TOML

```

[[send_email]]

name = "EMAIL"


[vars]

DOMAIN = "yourdomain.com"


```

TypeScript

```

interface Env {

  EMAIL: SendEmail;

  DOMAIN: string;

}


export default {

  async fetch(request: Request, env: Env): Promise<Response> {

    const url = new URL(request.url);


    if (url.pathname === "/send-invoice" && request.method === "POST") {

      return sendInvoiceWithPDF(request, env);

    }


    if (url.pathname === "/send-report" && request.method === "POST") {

      return sendReportWithImages(request, env);

    }


    if (url.pathname === "/upload-attachment" && request.method === "POST") {

      return sendEmailWithUpload(request, env);

    }


    return new Response("Not Found", { status: 404 });

  },

};


```

## PDF attachments

Generate and send PDF documents as email attachments:

TypeScript

```

async function sendInvoiceWithPDF(

  request: Request,

  env: Env,

): Promise<Response> {

  const { customerEmail, invoiceData } = await request.json();


  // Generate PDF content

  const pdfContent = generateInvoicePDF(invoiceData);


  await env.EMAIL.send({

    to: customerEmail,

    from: `billing@${env.DOMAIN}`,

    subject: `Invoice #${invoiceData.number}`,

    html: `

      <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">

        <h1>Invoice #${invoiceData.number}</h1>

        <p>Dear ${invoiceData.customerName},</p>


        <div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0;">

          <h3>Invoice Details</h3>

          <p><strong>Invoice Number:</strong> ${invoiceData.number}</p>

          <p><strong>Date:</strong> ${new Date(invoiceData.date).toLocaleDateString()}</p>

          <p><strong>Amount Due:</strong> $${invoiceData.total}</p>

          <p><strong>Due Date:</strong> ${new Date(invoiceData.dueDate).toLocaleDateString()}</p>

        </div>


        <p>Please find your invoice attached. Payment is due within 30 days.</p>

        <p>Thank you for your business!</p>

      </div>

    `,

    attachments: [

      {

        filename: `invoice-${invoiceData.number}.pdf`,

        content: new TextEncoder().encode(pdfContent).buffer,

        type: "application/pdf",

        disposition: "attachment",

      },

    ],

  });


  return new Response(

    JSON.stringify({ success: true, message: "Invoice sent" }),

  );

}


function generateInvoicePDF(invoiceData: any): string {

  // Simple PDF generation (in practice, use a proper PDF library)

  const pdfContent = `%PDF-1.4

1 0 obj<< /Type /Catalog /Pages 2 0 R >>endobj

2 0 obj<< /Type /Pages /Kids [3 0 R] /Count 1 >>endobj

3 0 obj<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>endobj

4 0 obj<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>endobj

5 0 obj<< /Length 200 >>stream

BT

/F1 24 Tf

50 700 Td

(INVOICE #${invoiceData.number}) Tj

0 -50 Td

/F1 12 Tf

(Customer: ${invoiceData.customerName}) Tj

0 -20 Td

(Date: ${new Date(invoiceData.date).toLocaleDateString()}) Tj

0 -20 Td

(Amount: $${invoiceData.total}) Tj

ET

endstream

endobj

xref

0 6

0000000000 65535 f

0000000010 00000 n

0000000053 00000 n

0000000100 00000 n

0000000200 00000 n

0000000300 00000 n

trailer<< /Size 6 /Root 1 0 R >>

startxref

400

%%EOF`;


  return pdfContent;

}


```

## Inline images

Embed images directly in email content using Content-ID references:

TypeScript

```

async function sendReportWithImages(

  request: Request,

  env: Env,

): Promise<Response> {

  const { recipientEmail, reportData } = await request.json();


  // Generate chart and logo images as SVG

  const chartImage = generateChartImage(reportData);

  const logoImage = getCompanyLogo();


  await env.EMAIL.send({

    to: recipientEmail,

    from: `reports@${env.DOMAIN}`,

    subject: `${reportData.title} - ${reportData.period}`,

    html: `

      <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">

        <!-- Company Logo (inline) -->

        <div style="text-align: center; margin-bottom: 30px;">

          <img src="cid:company-logo" alt="Company Logo" style="width: 200px; height: auto;">

        </div>


        <h1 style="color: #2563eb;">${reportData.title}</h1>

        <p style="color: #666;">Period: ${reportData.period}</p>


        <h2>Key Metrics</h2>

        <div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0;">

          <p><strong>Revenue:</strong> $${reportData.metrics.revenue.toLocaleString()}</p>

          <p><strong>New Users:</strong> ${reportData.metrics.users.toLocaleString()}</p>

          <p><strong>Growth Rate:</strong> ${reportData.metrics.growth}%</p>

        </div>


        <h2>Performance Chart</h2>

        <!-- Inline chart image -->

        <div style="text-align: center; margin: 20px 0;">

          <img src="cid:performance-chart" alt="Performance Chart"

               style="max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 4px;">

        </div>


        <p>For detailed analysis, please contact our team.</p>

      </div>

    `,

    attachments: [

      {

        filename: "company-logo.svg",

        content: logoImage,

        type: "image/svg+xml",

        disposition: "inline",

        contentId: "company-logo",

      },

      {

        filename: "performance-chart.svg",

        content: chartImage,

        type: "image/svg+xml",

        disposition: "inline",

        contentId: "performance-chart",

      },

    ],

  });


  return new Response(

    JSON.stringify({ success: true, message: "Report sent" }),

  );

}


function generateChartImage(reportData: any): ArrayBuffer {

  // Generate simple SVG chart (in practice, use a chart library)

  const chartSVG = `

    <svg width="400" height="200" xmlns="http://www.w3.org/2000/svg">

      <rect width="400" height="200" fill="#f8f9fa" stroke="#dee2e6"/>

      <rect x="50" y="150" width="30" height="${Math.max(10, reportData.metrics.growth * 2)}" fill="#2563eb"/>

      <rect x="100" y="120" width="30" height="50" fill="#28a745"/>

      <rect x="150" y="100" width="30" height="70" fill="#ffc107"/>

      <rect x="200" y="80" width="30" height="90" fill="#dc3545"/>

      <text x="200" y="30" text-anchor="middle" font-family="Arial" font-size="16">Performance Chart</text>

    </svg>

  `;


  return new TextEncoder().encode(chartSVG).buffer;

}


function getCompanyLogo(): ArrayBuffer {

  // Simple SVG logo

  const logoSVG = `

    <svg width="200" height="60" xmlns="http://www.w3.org/2000/svg">

      <rect width="200" height="60" fill="#2563eb" rx="8"/>

      <text x="100" y="35" text-anchor="middle" font-family="Arial"

            font-size="24" font-weight="bold" fill="white">Company</text>

    </svg>

  `;


  return new TextEncoder().encode(logoSVG).buffer;

}


```

## File uploads

Handle file uploads and send them as email attachments with validation:

TypeScript

```

async function sendEmailWithUpload(

  request: Request,

  env: Env,

): Promise<Response> {

  const formData = await request.formData();

  const file = formData.get("file") as File;

  const recipientEmail = formData.get("email") as string;

  const message = formData.get("message") as string;

  const senderName = (formData.get("senderName") as string) || "Someone";


  if (!file || !recipientEmail) {

    return new Response(

      JSON.stringify({

        error: "Missing required fields: file and email",

      }),

      { status: 400 },

    );

  }


  // Validate file

  const validation = validateFile(file);

  if (!validation.valid) {

    return new Response(

      JSON.stringify({

        error: validation.error,

      }),

      { status: 400 },

    );

  }


  await env.EMAIL.send({

    to: recipientEmail,

    from: `uploads@${env.DOMAIN}`,

    subject: `File shared: ${file.name}`,

    html: `

      <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">

        <h1 style="color: #2563eb;">File Shared</h1>


        <div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0;">

          <p><strong>${senderName}</strong> has shared a file with you.</p>

          ${message ? `<p><em>"${message}"</em></p>` : ""}

        </div>


        <div style="border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; margin: 20px 0;">

          <h3 style="margin: 0 0 15px 0;">File Details</h3>

          <p><strong>Filename:</strong> ${file.name}</p>

          <p><strong>Size:</strong> ${formatFileSize(file.size)}</p>

          <p><strong>Type:</strong> ${getFileTypeDescription(file.type)}</p>

        </div>


        <p>The file is attached to this email for download.</p>


        <div style="background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 4px;">

          <p style="margin: 0; color: #856404;">

            <strong>Security notice:</strong> Always verify the source before opening attachments.

          </p>

        </div>

      </div>

    `,

    attachments: [

      {

        filename: file.name,

        content: await file.arrayBuffer(),

        type: file.type,

        disposition: "attachment",

      },

    ],

  });


  return new Response(

    JSON.stringify({

      success: true,

      message: `File ${file.name} sent to ${recipientEmail}`,

    }),

  );

}


function validateFile(file: File): { valid: boolean; error?: string } {

  // 5 MiB matches the general outbound message size limit. Messages up to

  // 25 MiB are accepted only when sent to verified destination addresses.

  // Refer to /email-service/platform/limits/ for details.

  const maxSize = 5 * 1024 * 1024;

  const allowedTypes = [

    "image/jpeg",

    "image/png",

    "image/gif",

    "application/pdf",

    "text/plain",

    "text/csv",

    "application/msword",

    "application/vnd.openxmlformats-officedocument.wordprocessingml.document",

    "application/vnd.ms-excel",

    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",

    "application/zip",

  ];


  if (file.size === 0) {

    return { valid: false, error: "File is empty" };

  }


  if (file.size > maxSize) {

    return {

      valid: false,

      error: `File too large (max ${formatFileSize(maxSize)})`,

    };

  }


  if (!allowedTypes.includes(file.type)) {

    return { valid: false, error: "File type not allowed" };

  }


  // Check for dangerous extensions

  const dangerousExtensions = [".exe", ".bat", ".cmd", ".scr", ".vbs", ".js"];

  const fileName = file.name.toLowerCase();

  if (dangerousExtensions.some((ext) => fileName.endsWith(ext))) {

    return {

      valid: false,

      error: "File extension not allowed for security reasons",

    };

  }


  return { valid: true };

}


function formatFileSize(bytes: number): string {

  if (bytes === 0) return "0 Bytes";

  const k = 1024;

  const sizes = ["Bytes", "KB", "MB", "GB"];

  const i = Math.floor(Math.log(bytes) / Math.log(k));

  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];

}


function getFileTypeDescription(mimeType: string): string {

  const typeMap: Record<string, string> = {

    "image/jpeg": "JPEG Image",

    "image/png": "PNG Image",

    "image/gif": "GIF Image",

    "application/pdf": "PDF Document",

    "text/plain": "Text Document",

    "application/msword": "Word Document",

    "application/vnd.ms-excel": "Excel Spreadsheet",

    "application/zip": "ZIP Archive",

  };


  return typeMap[mimeType] || "Unknown";

}


```

## Next steps

* [Send method](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/) — full reference for the `Attachment` interface and the `send()` method.
* [Limits](https://developers.cloudflare.com/email-service/platform/limits/) — message size and attachment limits.
* [Email headers](https://developers.cloudflare.com/email-service/reference/headers/) — set custom headers alongside attachments.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/email-service/","name":"Email Service"}},{"@type":"ListItem","position":3,"item":{"@id":"/email-service/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/examples/email-sending/","name":"Email sending"}},{"@type":"ListItem","position":5,"item":{"@id":"/email-service/examples/email-sending/email-attachments/","name":"Email attachments"}}]}
```
