---
title: Cloudflare Email Service
description: Cloudflare Email Service provides powerful email capabilities:
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Cloudflare Email Service

Send transactional emails and route incoming emails to Workers or email addresses

 Available on Workers Paid plan 

Note

Cloudflare Email Service is currently in beta. Features and APIs may change before general availability.

Cloudflare Email Service provides powerful email capabilities:

* **Email Sending** for outbound transactional emails
* **Email Routing** for handling incoming emails with Workers or routing to email addresses

Together, these two features make it possible for you to send and receive emails from your applications. For example, you can use Email Service for:

* Transactional emails (welcome messages, password resets, order confirmations)
* Authentication flows (magic links, email verification, two-factor authentication)
* Notifications and alerts
* Custom email addresses (support@, contact@, orders@)
* Emails as a mode of interaction for agents, such as send an email to create an issue in ticket tracking

Access Email Service using the [REST API](https://developers.cloudflare.com/email-service/api/send-emails/rest-api/) from any platform, or directly from Cloudflare Workers using [bindings](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/):

* [ REST API (curl) ](#tab-panel-6790)
* [ index.ts (Workers) ](#tab-panel-6791)
* [ wrangler.jsonc ](#tab-panel-6792)

Terminal window

```

curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/email/sending/send" \

  --header "Authorization: Bearer <API_TOKEN>" \

  --header "Content-Type: application/json" \

  --data '{

    "to": "user@example.com",

    "from": "welcome@yourdomain.com",

    "subject": "Welcome to our service!",

    "html": "<h1>Welcome!</h1><p>Thanks for signing up.</p>",

    "text": "Welcome! Thanks for signing up."

  }'


```

Explain Code

TypeScript

```

export default {

  // Handle HTTP requests (Email Sending)

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

    // Send a welcome email

    await env.EMAIL.send({

      to: "user@example.com",

      from: "welcome@yourdomain.com",

      subject: "Welcome to our service!",

      html: "<h1>Welcome!</h1><p>Thanks for signing up.</p>",

      text: "Welcome! Thanks for signing up."

    });


      return new Response("Email sent successfully");

    },


    // Handle incoming emails (Email Routing)

    async email(message, env, ctx): Promise<void> {

      // Forward to support team

      if (message.to.includes("support@yourdomain.com")) {

        await message.forward("team@yourdomain.com");

      }


      // Send auto-reply

      await env.EMAIL.send({

        to: message.from,

        from: "noreply@yourdomain.com",

        subject: "We received your message",

        html: "<h1>Thank you!</h1><p>We'll get back to you soon.</p>"

      });

    }


} satisfies ExportedHandler<{ EMAIL: SendEmail }>;


```

Explain Code

JSONC

```

{

  "$schema": "node_modules/wrangler/config-schema.json",

  "name": "<ENTER_WORKER_NAME>",

  "main": "src/index.ts",

  "compatibility_date": "2024-01-01",


  // Email sending

  "send_email": [

    {

      "name": "EMAIL"

    }

  ],


  // Email routing

  "email": [

    {

      "name": "EMAIL_HANDLER"

    }

  ]

}


```

Explain Code

See the full [API reference](https://developers.cloudflare.com/email-service/api/send-emails/) for the REST API and Workers binding.

[ Get started ](https://developers.cloudflare.com/email-service/get-started/) 

---

## Features

###  Email Sending 

Send transactional emails with high deliverability and global performance.

[ Use Email Sending ](https://developers.cloudflare.com/email-service/get-started/send-emails/) 

###  Email Routing 

Route incoming emails to custom addresses, Workers, or external destinations.

[ Use Email Routing ](https://developers.cloudflare.com/email-service/get-started/route-emails/) 

###  Deliverability 

Automatic IP reputation management and deliverability optimization.

[ Use Deliverability ](https://developers.cloudflare.com/email-service/concepts/deliverability/) 

###  Analytics & Observability 

Monitor email performance with comprehensive metrics and alerting.

[ Use Analytics & Observability ](https://developers.cloudflare.com/email-service/observability/) 

###  API 

Send and route emails using the [REST API](https://developers.cloudflare.com/email-service/api/send-emails/rest-api/) or [Workers binding](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/).

[ Use API ](https://developers.cloudflare.com/email-service/api/) 

---

## Related products

**[Workers](https://developers.cloudflare.com/workers/)** 

Build serverless applications that can send emails directly from the edge.

**[Queues](https://developers.cloudflare.com/queues/)** 

Process email events asynchronously with Workers Queues integration.

**[Analytics Engine](https://developers.cloudflare.com/analytics/)** 

Store and analyze custom email metrics with Workers Analytics Engine.

---

### More resources

[Platform limits](https://developers.cloudflare.com/email-service/platform/limits/) 

Learn about Email Service limits and quotas.

[Pricing](https://developers.cloudflare.com/email-service/platform/pricing/) 

Understand Email Service pricing and plans.

[Examples](https://developers.cloudflare.com/email-service/examples/) 

Explore practical examples and implementation patterns.

[Discord](https://discord.com/channels/595317990191398933/893253103695065128) 

Ask questions and discuss Email Service with other developers.

[Twitter](https://x.com/cloudflaredev) 

Follow product announcements and developer updates.

```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"}}]}
```

---

---
title: Getting started
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/get-started/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Getting started

```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/get-started/","name":"Getting started"}}]}
```

---

---
title: Route emails
description: Route incoming emails sent to your domain to existing mailboxes, Workers for processing, or other destinations.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/get-started/route-emails.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Route emails

Set up email routing to forward incoming emails to existing mailboxes or process them with Workers.

Route incoming emails sent to your domain to existing mailboxes, Workers for processing, or other destinations.

Note

You must be using Cloudflare DNS to use Email Service.

## Prerequisites

1. Sign up for a [Cloudflare account ↗](https://dash.cloudflare.com/sign-up/workers-and-pages).
2. Install [Node.js ↗](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).

Node.js version manager

Use a Node version manager like [Volta ↗](https://volta.sh/) or [nvm ↗](https://github.com/nvm-sh/nvm) to avoid permission issues and change Node.js versions. [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/), discussed later in this guide, requires a Node version of `16.17.0` or later.

## Set up your domain

Before using Email Routing, you need to configure your domain.

1. In the Cloudflare dashboard, go to **Email Routing**.  
[ Go to **Email Routing** ](https://dash.cloudflare.com/?to=/:account/email-service/routing)
2. Select **Onboard Domain**.
3. Choose a domain from your Cloudflare account.
4. Select **Continue** to proceed with DNS configuration.
5. Select **Add records and onboard**. This will add the following DNS records to your root domain:  
   * MX records to route incoming emails to Cloudflare.  
   * TXT record for SPF to authorize email routing.  
   * TXT record for DKIM to provide authentication for routed emails.

DNS Propagation

DNS changes can take up to 24 hours to propagate globally, but usually complete within 5-15 minutes for domains using Cloudflare DNS.

Once your domain is onboarded, you can start routing emails.

## Route your first email

You can route your first email by setting up forwarding rules in the dashboard, or by processing emails with Workers.

* [ Route to email ](#tab-panel-6827)
* [ Route to Workers ](#tab-panel-6828)

The simplest way to route emails is forwarding them to existing email addresses.

### Create a forwarding rule

1. In the Cloudflare dashboard, go to **Email Routing**.  
[ Go to **Email Routing** ](https://dash.cloudflare.com/?to=/:account/email-service/routing)
2. Select the domain you want to create an email address for.
3. Select the **Routing Rules** tab.
4. Select **Create Address**.
5. Configure your first rule (for instance, forwarding emails to `support@yourdomain.com` to your personal email address):  
   * **Custom address**: Enter the local part of the email (for example, `support` for `support@yourdomain.com`)  
   * **Action**: Send to an email  
   * **Destination**: Your personal email address (for example, `your-email@gmail.com`)
6. Select **Save**.

### Test your forwarding rule

Verify that your routing rule is working:

1. Send an email from another email account to your newly created address (for example, `support@yourdomain.com`).
2. Check the destination inbox for the forwarded email.
3. If you do not see the email right away, check your spam folder.

Use Workers to process emails with custom logic before forwarding or responding.

### Create an email processing Worker

1. Create a new Worker project:  
Terminal window  
```  
npm create cloudflare@latest email-processor  
```  
When prompted, select **"Hello World" Worker** as the template. Then navigate to the project directory:  
Terminal window  
```  
cd email-processor  
```
2. Install the required package for creating email replies:  
Terminal window  
```  
npm install mimetext  
```
3. Add the `nodejs_compat` compatibility flag to your Wrangler configuration file. This is required for the `mimetext` package:  
   * [  wrangler.jsonc ](#tab-panel-6825)  
   * [  wrangler.toml ](#tab-panel-6826)  
JSONC  
```  
{  
  "$schema": "./node_modules/wrangler/config-schema.json",  
  "compatibility_flags": [  
    "nodejs_compat"  
  ]  
}  
```  
TOML  
```  
compatibility_flags = ["nodejs_compat"]  
```
4. Create your email handler in `src/index.ts`:  
TypeScript  
```  
import { EmailMessage } from "cloudflare:email";  
import { createMimeMessage } from "mimetext";  
// ============================================  
// Configuration - Update these values  
// ============================================  
const YOUR_DOMAIN = "yourdomain.com"; // Replace with your verified domain  
const FORWARD_TO_EMAIL = "your-team@company.com"; // Replace with where you want emails forwarded  
export default {  
  async email(message, env, ctx): Promise<void> {  
    const sender = message.from;  
    const recipient = message.to;  
    const subject = message.headers.get("subject") || "";  
    console.log(  
      `Processing email from ${sender} to ${recipient} with subject ${subject}`,  
    );  
    // Route based on recipient  
    if (recipient.includes("support@")) {  
      // Send auto-reply  
      const msg = createMimeMessage();  
      const messageId = message.headers.get("Message-ID");  
      if (messageId) {  
        msg.setHeader("In-Reply-To", messageId);  
      }  
      msg.setSender({  
        name: "Support Team",  
        addr: `support@${YOUR_DOMAIN}`,  
      });  
      msg.setRecipient(message.from);  
      msg.setSubject("We received your message");  
      // Add plain text version  
      msg.addMessage({  
        contentType: "text/plain",  
        data: "Thank you for contacting support. Your ticket number is 123.\n\nA member of our support team will get back to you shortly.",  
      });  
      // Add HTML version  
      msg.addMessage({  
        contentType: "text/html",  
        data: "<p>Thank you for contacting support. Your ticket number is <strong>123</strong>.</p><p>A member of our support team will get back to you shortly.</p>",  
      });  
      const replyMessage = new EmailMessage(  
        `support@${YOUR_DOMAIN}`,  
        message.from,  
        msg.asRaw(),  
      );  
      await message.reply(replyMessage);  
      // Forward to support team  
      await message.forward(FORWARD_TO_EMAIL);  
    } else {  
      // Default: forward to admin  
      await message.forward(FORWARD_TO_EMAIL);  
    }  
  },  
} satisfies ExportedHandler<Env>;  
```  
Explain Code  
Update configuration  
Before deploying, update the constants at the top of the file:  
   * `YOUR_DOMAIN`: Your verified domain from the Cloudflare dashboard  
   * `FORWARD_TO_EMAIL`: The email address where you want to receive forwarded emails
5. Deploy your Worker:  
Terminal window  
```  
npm run deploy  
```

### Configure routing to Worker

1. In the Cloudflare dashboard, go to **Email Routing**.  
[ Go to **Email Routing** ](https://dash.cloudflare.com/?to=/:account/email-service/routing)
2. Select the domain you want to configure routing for.
3. Select the **Routing Rules** tab.
4. Select **Create Address**.
5. Configure Worker routing:  
   * **Custom address**: Enter the local part of the email (for example, `support` for `support@yourdomain.com`)  
   * **Action**: Send to a Worker  
   * **Worker**: Select your `email-processor` Worker
6. Select **Save**.

### Test your email routing

After configuring the routing rule, test that it works:

1. Send an email from your personal email account to the address you configured (for example, `support@yourdomain.com`).
2. Check your `FORWARD_TO_EMAIL` inbox for the forwarded email.
3. If the recipient email was `support@`, you should also receive an auto-reply at your personal email address.

## Next steps

Now that you can route emails, explore advanced features:

* **[Send outbound emails](https://developers.cloudflare.com/email-service/get-started/send-emails/)** \- Send emails from your applications
* **[API reference](https://developers.cloudflare.com/email-service/api/route-emails/)** \- Complete routing API documentation
* **[Examples](https://developers.cloudflare.com/email-service/examples/)** \- Real-world routing patterns

```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/get-started/","name":"Getting started"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/get-started/route-emails/","name":"Route emails"}}]}
```

---

---
title: Send emails
description: Send emails from your applications using Cloudflare Email Service. You can use the REST API from any platform or the Workers binding for applications built on Cloudflare Workers.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/get-started/send-emails.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Send emails

Send your first email using the REST API or Workers binding.

Send emails from your applications using Cloudflare Email Service. You can use the **REST API** from any platform or the **Workers binding** for applications built on Cloudflare Workers.

Note

You must be using Cloudflare DNS to use Email Service.

## Prerequisites

1. Sign up for a [Cloudflare account ↗](https://dash.cloudflare.com/sign-up/workers-and-pages).
2. Install [Node.js ↗](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).

Node.js version manager

Use a Node version manager like [Volta ↗](https://volta.sh/) or [nvm ↗](https://github.com/nvm-sh/nvm) to avoid permission issues and change Node.js versions. [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/), discussed later in this guide, requires a Node version of `16.17.0` or later.

## Set up your domain

Before using Email Sending, you need to configure your domain.

1. In the Cloudflare dashboard, go to **Email Sending**.  
[ Go to **Email Sending** ](https://dash.cloudflare.com/?to=/:account/email-service/sending)
2. Select **Onboard Domain**.
3. Choose a domain from your Cloudflare account.
4. Select **Continue** to proceed with DNS configuration.
5. Select **Add records and onboard**. This will add the following DNS records on the `cf-bounce` subdomain of your domain:  
   * MX records for bounce handling.  
   * TXT record for SPF to authorize sending emails.  
   * TXT record for DKIM to provide authentication for emails sent from your domain.  
   * TXT record for DMARC on `_dmarc.yourdomain.com`.

DNS Propagation

DNS changes can take up to 24 hours to propagate globally, but usually complete within 5-15 minutes for domains using Cloudflare DNS.

Once your domain is onboarded, you can start sending emails.

## Send your first email with the REST API

Send an email with a single `curl` command. Replace `<ACCOUNT_ID>` with your [Cloudflare account ID](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/) and `<API_TOKEN>` with an [API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/).

Terminal window

```

curl "https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/email/sending/send" \

  --header "Authorization: Bearer <API_TOKEN>" \

  --header "Content-Type: application/json" \

  --data '{

    "to": "recipient@example.com",

    "from": "welcome@yourdomain.com",

    "subject": "Welcome to our service!",

    "html": "<h1>Welcome!</h1><p>Thanks for signing up.</p>",

    "text": "Welcome! Thanks for signing up."

  }'


```

Explain Code

A successful response includes the delivery status for each recipient:

```

{

  "success": true,

  "errors": [],

  "messages": [],

  "result": {

    "delivered": ["recipient@example.com"],

    "permanent_bounces": [],

    "queued": []

  }

}


```

Explain Code

For more details, see the [REST API reference](https://developers.cloudflare.com/email-service/api/send-emails/rest-api/).

## Send your first email with Workers

If you are building on Cloudflare Workers, you can use the Workers binding for native email sending. Start by creating a new Worker project.

1. Create a new Worker project:  
 npm  yarn  pnpm  
```  
npm create cloudflare@latest -- email-service-tutorial  
```  
```  
yarn create cloudflare email-service-tutorial  
```  
```  
pnpm create cloudflare@latest email-service-tutorial  
```  
When prompted, select **"Hello World" Worker** as the template.
2. Add the email binding to your Wrangler configuration file:  
   * [  wrangler.jsonc ](#tab-panel-6829)  
   * [  wrangler.toml ](#tab-panel-6830)  
JSONC  
```  
{  
  "$schema": "./node_modules/wrangler/config-schema.json",  
  "send_email": [  
    {  
      "name": "EMAIL",  
      "remote": true  
    }  
  ]  
}  
```  
TOML  
```  
[[send_email]]  
name = "EMAIL"  
remote = true  
```
3. Create your Worker code in `src/index.ts`:  
TypeScript  
```  
// Configuration - Update these values  
const YOUR_DOMAIN = "yourdomain.com"; // Replace with your verified domain  
const RECIPIENT_EMAIL = "recipient@example.com"; // Replace with your email to receive test emails  
export default {  
  async fetch(request: Request, env: Env): Promise<Response> {  
    // Send a welcome email  
    const response = await env.EMAIL.send({  
      to: RECIPIENT_EMAIL,  
      from: `welcome@${YOUR_DOMAIN}`,  
      subject: "Welcome to our service!",  
      html: "<h1>Welcome!</h1><p>Thanks for signing up.</p>",  
      text: "Welcome! Thanks for signing up.",  
    });  
    return new Response(`Email sent: ${response.messageId}`);  
  },  
} satisfies ExportedHandler<Env>;  
```  
Explain Code
4. You can use `npx wrangler dev` to develop your Worker project and send emails. This runs your code locally while connecting to Cloudflare Email Service (using [remote bindings](https://developers.cloudflare.com/workers/development-testing/#remote-bindings)).  
Terminal window  
```  
npx wrangler dev  
# ⎔ Starting remote preview...  
# Total Upload: 24.96 KiB / gzip: 6.17 KiB  
# [wrangler:info] Ready on http://localhost:8787  
```
5. Deploy your Worker:  
Terminal window  
```  
npm run deploy  
```

## Test your email Worker

After deploying, test that your Worker can send emails:

1. Visit your Worker URL in a browser (shown in the deploy output, for example: `https://email-service-tutorial.<your-subdomain>.workers.dev`).
2. You should see a response like `Email sent: <message-id>`.
3. Check the inbox for the email address you specified in `RECIPIENT_EMAIL`. If you do not see the email, check your spam folder.

## Next steps

Now that you can send emails, explore advanced features:

* **[Route incoming emails](https://developers.cloudflare.com/email-service/get-started/route-emails/)** \- Process emails sent to your domain
* **[API reference](https://developers.cloudflare.com/email-service/api/send-emails/)** \- Complete API documentation
* **[Examples](https://developers.cloudflare.com/email-service/examples/)** \- Real-world implementation patterns

```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/get-started/","name":"Getting started"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/get-started/send-emails/","name":"Send emails"}}]}
```

---

---
title: Workers API
description: Process incoming emails using the email() handler in your Cloudflare Workers. This allows you to programmatically handle email routing with custom logic.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/api/route-emails/email-handler.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Workers API

Process incoming emails with the email() handler in Cloudflare Workers. Forward, reply, reject, or process emails programmatically.

Process incoming emails using the `email()` handler in your Cloudflare Workers. This allows you to programmatically handle email routing with custom logic.

## Email handler syntax

Add the `email` handler function to your Worker's exported handlers:

TypeScript

```

interface Env {

  EMAIL: SendEmail;

}


export default {

  async email(message, env, ctx): Promise<void> {

    // Process incoming email

    await message.forward("destination@example.com");

  },

} satisfies ExportedHandler<Env>;


```

Explain Code

### Parameters

| Parameter | Type                    | Description                                   |
| --------- | ----------------------- | --------------------------------------------- |
| message   | ForwardableEmailMessage | The incoming email message                    |
| env       | object                  | Worker environment bindings (KV, EMAIL, etc.) |
| ctx       | object                  | Execution context with waitUntil function     |

## `ForwardableEmailMessage` interface

The `message` parameter provides access to the incoming email:

TypeScript

```

interface ForwardableEmailMessage {

  readonly from: string; // Sender email address (envelope MAIL FROM)

  readonly to: string; // Recipient email address (envelope RCPT TO)

  readonly headers: Headers; // Email headers (Subject, Message-ID, etc.)

  readonly raw: ReadableStream; // Raw MIME email content stream

  readonly rawSize: number; // Size of raw email in bytes

  readonly canBeForwarded: boolean; // Whether the message can be forwarded


  // Actions

  setReject(reason: string): void;

  forward(rcptTo: string, headers?: Headers): Promise<void>;

  reply(message: EmailMessage): Promise<void>;

}


```

Explain Code

### Properties

* [ Basic properties ](#tab-panel-6793)
* [ Reading content ](#tab-panel-6794)
* [ Parse email content ](#tab-panel-6795)

TypeScript

```

export default {

  async email(message, env, ctx): Promise<void> {

    // Access email metadata

    console.log(`From: ${message.from}`);

    console.log(`To: ${message.to}`);

    console.log(`Size: ${message.rawSize} bytes`);


    // Access headers

    const subject = message.headers.get("subject");

    const date = message.headers.get("date");

    const messageId = message.headers.get("message-id");


    console.log(`Subject: ${subject}`);

    console.log(`Date: ${date}`);

    console.log(`Message-ID: ${messageId}`);

  },

};


```

Explain Code

TypeScript

```

export default {

  async email(message, env, ctx): Promise<void> {

    // Read raw email content

    const reader = message.raw.getReader();

    const chunks = [];


    try {

      while (true) {

        const { done, value } = await reader.read();

        if (done) break;

        chunks.push(value);

      }


      // Convert to string

      const decoder = new TextDecoder();

      const rawContent = decoder.decode(

        new Uint8Array(chunks.reduce((acc, chunk) => [...acc, ...chunk], [])),

      );


      console.log("Raw email content:", rawContent);

    } finally {

      reader.releaseLock();

    }

  },

};


```

Explain Code

TypeScript

```

// Helper function to parse email content

async function parseEmailContent(

  message,

): Promise<{ subject: string; textBody: string; htmlBody: string }> {

  const reader = message.raw.getReader();

  const chunks = [];


  try {

    while (true) {

      const { done, value } = await reader.read();

      if (done) break;

      chunks.push(value);

    }


    const decoder = new TextDecoder();

    const rawContent = decoder.decode(

      new Uint8Array(chunks.reduce((acc, chunk) => [...acc, ...chunk], [])),

    );


    // Parse MIME content (simplified)

    const subject = message.headers.get("subject") || "";

    const textMatch = rawContent.match(

      /Content-Type: text\/plain[\s\S]*?\n\n([\s\S]*?)(?=\n--|\nContent-Type|\n$)/,

    );

    const htmlMatch = rawContent.match(

      /Content-Type: text\/html[\s\S]*?\n\n([\s\S]*?)(?=\n--|\nContent-Type|\n$)/,

    );


    return {

      subject,

      textBody: textMatch ? textMatch[1].trim() : "",

      htmlBody: htmlMatch ? htmlMatch[1].trim() : "",

    };

  } finally {

    reader.releaseLock();

  }

}


export default {

  async email(message, env, ctx): Promise<void> {

    const { subject, textBody, htmlBody } = await parseEmailContent(message);


    console.log(`Subject: ${subject}`);

    console.log(`Text: ${textBody}`);

    console.log(`HTML: ${htmlBody}`);

  },

};


```

Explain Code

## Email actions

### Forward emails

Forward incoming emails to verified destination addresses:

* [ Simple forwarding ](#tab-panel-6796)
* [ Conditional forwarding ](#tab-panel-6797)
* [ Multiple forwarding ](#tab-panel-6798)

TypeScript

```

export default {

  async email(message, env, ctx): Promise<void> {

    // Forward to a single address

    await message.forward("team@company.com");

  },

};


```

TypeScript

```

export default {

  async email(message, env, ctx): Promise<void> {

    const recipient = message.to;

    const subject = message.headers.get("subject") || "";


    // Route based on recipient

    if (recipient.includes("support@")) {

      await message.forward("support-team@company.com");

    } else if (recipient.includes("sales@")) {

      await message.forward("sales-team@company.com");

    } else if (subject.toLowerCase().includes("urgent")) {

      await message.forward("urgent@company.com");

    } else {

      // Default routing

      await message.forward("general@company.com");

    }

  },

};


```

Explain Code

TypeScript

```

export default {

  async email(message, env, ctx): Promise<void> {

    const subject = message.headers.get("subject") || "";


    if (subject.toLowerCase().includes("security")) {

      // Forward to multiple addresses for security issues

      await Promise.all([

        message.forward("security@company.com"),

        message.forward("admin@company.com"),

        message.forward("ciso@company.com"),

      ]);

    } else {

      await message.forward("general@company.com");

    }

  },

};


```

Explain Code

### Forward with custom headers

Add custom headers when forwarding:

TypeScript

```

export default {

  async email(message, env, ctx): Promise<void> {

    // Create custom headers

    const customHeaders = new Headers();

    customHeaders.set("X-Processed-By", "Email-Worker");

    customHeaders.set("X-Processing-Time", new Date().toISOString());

    customHeaders.set("X-Original-Recipient", message.to);

    customHeaders.set("X-Spam-Score", "0.1"); // Example spam score


    // Forward with custom headers

    await message.forward("processed@company.com", customHeaders);

  },

};


```

Explain Code

### Reply to emails

Send automatic replies using the Email Service binding:

* [ Simple auto-reply ](#tab-panel-6799)
* [ Smart auto-reply ](#tab-panel-6800)

TypeScript

```

interface Env {

  EMAIL: SendEmail;

}


export default {

  async email(message, env, ctx): Promise<void> {

    const subject = message.headers.get("subject") || "";


    // Send auto-reply

    await env.EMAIL.send({

      to: message.from,

      from: message.to, // Reply from the original recipient address

      subject: `Re: ${subject}`,

      html: `

                <h1>Thank you for your message</h1>

                <p>We have received your email and will respond shortly.</p>

                <p>Original message received at: ${new Date().toISOString()}</p>

            `,

      text: "Thank you for your message. We have received your email and will respond shortly.",

    });


    // Also forward to human team

    await message.forward("team@company.com");

  },

};


```

Explain Code

TypeScript

```

export default {

  async email(message, env, ctx): Promise<void> {

    const sender = message.from;

    const recipient = message.to;

    const subject = message.headers.get("subject") || "";


    // Don't reply to automated emails

    if (

      sender.includes("noreply") ||

      sender.includes("no-reply") ||

      subject.toLowerCase().includes("automated")

    ) {

      await message.forward("team@company.com");

      return;

    }


    // Customized auto-reply based on recipient

    let replyMessage = "";

    let replySubject = `Re: ${subject}`;


    if (recipient.includes("support@")) {

      replyMessage = `

                <h1>Support Request Received</h1>

                <p>Thank you for contacting support. Your request has been assigned ticket #${Date.now()}.</p>

                <p>Expected response time: 2-4 hours during business hours.</p>

            `;

    } else if (recipient.includes("sales@")) {

      replyMessage = `

                <h1>Sales Inquiry Received</h1>

                <p>Thank you for your interest in our products.</p>

                <p>A sales representative will contact you within 24 hours.</p>

            `;

    } else {

      replyMessage = `

                <h1>Message Received</h1>

                <p>Thank you for your message. We will respond within 2 business days.</p>

            `;

    }


    await env.EMAIL.send({

      to: sender,

      from: recipient,

      subject: replySubject,

      html: replyMessage,

      text: replyMessage.replace(/<[^>]*>/g, ""), // Strip HTML for text version

    });


    // Forward to appropriate team

    await message.forward("team@company.com");

  },

};


```

Explain Code

### Reject emails

Reject emails with a permanent SMTP error:

* [ Simple rejection ](#tab-panel-6801)
* [ Content-based rejection ](#tab-panel-6802)

TypeScript

```

export default {

  async email(message, env, ctx): Promise<void> {

    const sender = message.from;


    // Block specific senders

    const blockedDomains = ["spam.com", "unwanted.net"];

    const senderDomain = sender.split("@")[1];


    if (blockedDomains.includes(senderDomain)) {

      message.setReject("Sender domain not allowed");

      return;

    }


    // Continue processing

    await message.forward("inbox@company.com");

  },

};


```

Explain Code

TypeScript

```

export default {

  async email(message, env, ctx): Promise<void> {

    const subject = message.headers.get("subject") || "";


    // Reject based on subject content

    const spamKeywords = ["buy now", "limited time", "act fast", "urgent"];

    const containsSpam = spamKeywords.some((keyword) =>

      subject.toLowerCase().includes(keyword),

    );


    if (containsSpam) {

      message.setReject("Message appears to be spam");

      return;

    }


    // Check message size

    if (message.rawSize > 25 * 1024 * 1024) {

      // 25MB limit

      message.setReject("Message too large");

      return;

    }


    // Continue processing

    await message.forward("inbox@company.com");

  },

};


```

Explain Code

## Error handling

Handle errors gracefully in email processing:

TypeScript

```

export default {

  async email(message, env, ctx): Promise<void> {

    try {

      // Main email processing logic

      await processEmail(message, env);

    } catch (error) {

      console.error("Email processing failed:", error);


      // Log error for monitoring

      if (env.ERROR_LOGS) {

        await env.ERROR_LOGS.put(

          `error-${Date.now()}`,

          JSON.stringify({

            error: error.message,

            stack: error.stack,

            from: message.from,

            to: message.to,

            timestamp: new Date().toISOString(),

          }),

        );

      }


      // Fallback: forward to admin

      try {

        await message.forward("admin@company.com");

      } catch (fallbackError) {

        console.error("Fallback forwarding failed:", fallbackError);

        // Last resort: reject the email

        message.setReject("Internal processing error");

      }

    }

  },

};


async function processEmail(message, env) {

  // Your main email processing logic here

  const recipient = message.to;


  if (recipient.includes("support@")) {

    await message.forward("support@company.com");

  } else if (recipient.includes("sales@")) {

    await message.forward("sales@company.com");

  } else {

    await message.forward("general@company.com");

  }

}


```

Explain Code

---

**Next steps:**

* Test locally: [Email routing development](https://developers.cloudflare.com/email-service/local-development/routing/)
* Set up [email routing configuration](https://developers.cloudflare.com/email-service/configuration/email-routing-addresses/)
* See [email routing examples](https://developers.cloudflare.com/email-service/examples/email-routing/) for advanced email processing
* Learn about [spam filtering](https://developers.cloudflare.com/email-service/examples/email-routing/spam-filtering/) with Workers

```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/api/","name":"API reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/api/route-emails/","name":"Route emails"}},{"@type":"ListItem","position":5,"item":{"@id":"/email-service/api/route-emails/email-handler/","name":"Workers API"}}]}
```

---

---
title: REST API
description: The REST API allows you to send emails from any application using standard HTTP requests. Use it from any backend, serverless function, or CI/CD pipeline — no Cloudflare Workers binding is required.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/api/send-emails/rest-api.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# REST API

The REST API allows you to send emails from any application using standard HTTP requests. Use it from any backend, serverless function, or CI/CD pipeline — no Cloudflare Workers binding is required.

For the full OpenAPI specification, refer to the [Email Sending API reference ↗](https://developers.cloudflare.com/api/resources/email%5Fsending/methods/send).

## Endpoint

```

POST https://api.cloudflare.com/client/v4/accounts/{account_id}/email/sending/send


```

## Authentication

Authenticate with a [Cloudflare API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) that has permission to send emails. Include it in the `Authorization` header:

```

Authorization: Bearer <API_TOKEN>


```

## Send an email

* [ Simple email ](#tab-panel-6803)
* [ Multiple recipients ](#tab-panel-6804)
* [ With CC and BCC ](#tab-panel-6805)

Terminal window

```

curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/email/sending/send" \

  --header "Authorization: Bearer <API_TOKEN>" \

  --header "Content-Type: application/json" \

  --data '{

    "to": "recipient@example.com",

    "from": "welcome@yourdomain.com",

    "subject": "Welcome to our service!",

    "html": "<h1>Welcome!</h1><p>Thanks for signing up.</p>",

    "text": "Welcome! Thanks for signing up."

  }'


```

Explain Code

Terminal window

```

curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/email/sending/send" \

  --header "Authorization: Bearer <API_TOKEN>" \

  --header "Content-Type: application/json" \

  --data '{

    "to": ["user1@example.com", "user2@example.com"],

    "from": { "address": "newsletter@yourdomain.com", "name": "Newsletter Team" },

    "subject": "Monthly Newsletter",

    "html": "<h1>This month'\''s updates</h1>",

    "text": "This month'\''s updates"

  }'


```

Explain Code

Terminal window

```

curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/email/sending/send" \

  --header "Authorization: Bearer <API_TOKEN>" \

  --header "Content-Type: application/json" \

  --data '{

    "to": "customer@example.com",

    "cc": ["manager@company.com"],

    "bcc": ["archive@company.com"],

    "from": "orders@yourdomain.com",

    "reply_to": "support@yourdomain.com",

    "subject": "Order Confirmation #12345",

    "html": "<h1>Your order is confirmed</h1>",

    "text": "Your order is confirmed"

  }'


```

Explain Code

## Attachments

Send files by including base64-encoded content in the `attachments` array:

Terminal window

```

curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/email/sending/send" \

  --header "Authorization: Bearer <API_TOKEN>" \

  --header "Content-Type: application/json" \

  --data '{

    "to": "customer@example.com",

    "from": "invoices@yourdomain.com",

    "subject": "Your Invoice",

    "html": "<h1>Invoice attached</h1><p>Please find your invoice attached.</p>",

    "attachments": [

      {

        "content": "JVBERi0xLjQKJeLjz9MK...",

        "filename": "invoice-12345.pdf",

        "type": "application/pdf",

        "disposition": "attachment"

      }

    ]

  }'


```

Explain Code

## Custom headers

Set custom headers for threading, list management, or tracking. Refer to the [email headers reference](https://developers.cloudflare.com/email-service/reference/headers/) for the full list of allowed headers.

Terminal window

```

curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/email/sending/send" \

  --header "Authorization: Bearer <API_TOKEN>" \

  --header "Content-Type: application/json" \

  --data '{

    "to": "user@example.com",

    "from": "notifications@yourdomain.com",

    "subject": "Your weekly digest",

    "html": "<h1>Weekly Digest</h1>",

    "headers": {

      "List-Unsubscribe": "<https://yourdomain.com/unsubscribe?id=abc123>",

      "List-Unsubscribe-Post": "List-Unsubscribe=One-Click",

      "X-Campaign-ID": "weekly-digest-2026-03"

    }

  }'


```

Explain Code

## Response

A successful response returns the delivery status for each recipient:

```

{

  "success": true,

  "errors": [],

  "messages": [],

  "result": {

    "delivered": ["recipient@example.com"],

    "permanent_bounces": [],

    "queued": []

  }

}


```

Explain Code

* `delivered` \- Email addresses to which the message was delivered immediately
* `permanent_bounces` \- Email addresses that permanently bounced
* `queued` \- Email addresses for which delivery was queued for later

## Error handling

The REST API returns standard Cloudflare API error responses. A failed request returns an `errors` array with numeric error codes and machine-readable messages:

```

{

  "success": false,

  "errors": [

    {

      "code": 10001,

      "message": "email.sending.error.invalid_request_schema"

    }

  ],

  "messages": [],

  "result": null

}


```

Explain Code

Common REST API error codes:

| HTTP Status | Code  | Message                                       | Description                            |
| ----------- | ----- | --------------------------------------------- | -------------------------------------- |
| 400         | 10001 | email.sending.error.invalid\_request\_schema  | Invalid request format                 |
| 400         | 10200 | email.sending.error.email.invalid             | Invalid email content                  |
| 400         | 10201 | email.sending.error.email.no\_content\_length | Missing content length                 |
| 400         | 10202 | email.sending.error.email.too\_big            | Email exceeds size limit               |
| 403         | 10203 | email.sending.error.email.sending\_disabled   | Sending disabled for this zone/account |
| 429         | 10004 | email.sending.error.throttled                 | Rate limit exceeded                    |
| 500         | 10002 | email.sending.error.internal\_server          | Internal server error                  |

Workers binding vs REST API errors

The REST API returns standard Cloudflare API numeric error codes, while the [Workers binding](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/) throws errors with string codes (for example, `E_SENDER_NOT_VERIFIED`). Refer to the [Workers API error codes table](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/#error-codes) for the string error codes.

## Next steps

* Refer to the [Email Sending API reference ↗](https://developers.cloudflare.com/api/resources/email%5Fsending/methods/send) for the full request and response schemas.
* See the [Workers API](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/) for sending emails directly from Cloudflare Workers using bindings.
* Review [email headers](https://developers.cloudflare.com/email-service/reference/headers/) for threading, list management, and custom tracking headers.

```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/api/","name":"API reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/api/send-emails/","name":"Send emails"}},{"@type":"ListItem","position":5,"item":{"@id":"/email-service/api/send-emails/rest-api/","name":"REST API"}}]}
```

---

---
title: Workers API
description: The Workers API provides native email sending capabilities directly from your Cloudflare Workers through bindings. If you are not using Workers, you can send emails using the REST API instead.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/api/send-emails/workers-api.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Workers API

The Workers API provides native email sending capabilities directly from your Cloudflare Workers through bindings. If you are not using Workers, you can send emails using the [REST API](https://developers.cloudflare.com/email-service/api/send-emails/rest-api/) instead.

## Email binding

Configure email bindings in your Wrangler configuration file to enable email sending:

* [  wrangler.jsonc ](#tab-panel-6812)
* [  wrangler.toml ](#tab-panel-6813)

JSONC

```

{

  "$schema": "./node_modules/wrangler/config-schema.json",

  "send_email": [

    {

      "name": "EMAIL"

    },

    {

      "name": "RESTRICTED_EMAIL",

      "allowed_sender_addresses": [

        "noreply@yourdomain.com",

        "support@yourdomain.com"

      ]

    }

  ]

}


```

Explain Code

TOML

```

[[send_email]]

name = "EMAIL"


# Optional: restrict sender addresses for security

[[send_email]]

name = "RESTRICTED_EMAIL"

allowed_sender_addresses = ["noreply@yourdomain.com", "support@yourdomain.com"]


```

## `send()` method

Send a single email using the `send()` method on your email binding.

### Interface

TypeScript

```

interface SendEmail {

  send(message: EmailMessage | EmailMessageBuilder): Promise<EmailSendResult>;

}


// Structured email builder (recommended)

interface EmailMessageBuilder {

  to: string | string[]; // Max 50 recipients

  from: string | { email: string; name: string };

  subject: string;

  html?: string;

  text?: string;

  cc?: string | string[];

  bcc?: string | string[];

  replyTo?: string | { email: string; name: string };

  attachments?: Attachment[];

  headers?: { [key: string]: string }; // See /email-service/reference/headers/

}


interface Attachment {

  content: string | ArrayBuffer; // Base64 string or binary content

  filename: string;

  type: string; // MIME type

  disposition: "attachment" | "inline";

  contentId?: string; // For inline attachments

}


interface EmailSendResult {

  messageId: string; // Unique email ID

}


// Errors are thrown as standard Error objects with a `code` property

// try { await env.EMAIL.send(...) } catch (e) { console.log(e.code, e.message) }


```

Explain Code

### Basic usage

* [ Simple email ](#tab-panel-6807)
* [ Multiple recipients ](#tab-panel-6808)
* [ With CC and BCC ](#tab-panel-6809)

TypeScript

```

interface Env {

  EMAIL: SendEmail;

}


```

TypeScript

```

// Send to multiple recipients (max 50)

const response = await env.EMAIL.send({

  to: ["user1@example.com", "user2@example.com", "user3@example.com"],

  from: { email: "newsletter@yourdomain.com", name: "Newsletter Team" },

  subject: "Monthly Newsletter",

  html: "<h1>This month's updates</h1>",

  text: "This month's updates",

});


```

TypeScript

```

const response = await env.EMAIL.send({

  to: "customer@example.com",

  cc: ["manager@company.com"],

  bcc: ["archive@company.com"],

  from: "orders@yourdomain.com",

  replyTo: "support@yourdomain.com",

  subject: "Order Confirmation #12345",

  html: "<h1>Your order is confirmed</h1>",

  text: "Your order is confirmed",

});


```

Explain Code

### Attachments

* [ PDF attachment ](#tab-panel-6810)
* [ Inline image ](#tab-panel-6811)

TypeScript

```

const response = await env.EMAIL.send({

  to: "customer@example.com",

  from: "invoices@yourdomain.com",

  subject: "Your Invoice",

  html: "<h1>Invoice attached</h1><p>Please find your invoice attached.</p>",

  attachments: [

    {

      content: "JVBERi0xLjQKJeLjz9MKMSAwIG9iag...", // Base64 PDF content

      filename: "invoice-12345.pdf",

      type: "application/pdf",

      disposition: "attachment",

    },

  ],

});


```

Explain Code

TypeScript

```

const response = await env.EMAIL.send({

  to: "user@example.com",

  from: "marketing@yourdomain.com",

  subject: "Check out our new product!",

  html: `

    <h1>New Product Launch</h1>

    <img src="cid:product-image" alt="New Product" />

    <p>Check out our amazing new product!</p>

  `,

  attachments: [

    {

      content: "iVBORw0KGgoAAAANSUhEUgAA...", // Base64 image content

      filename: "product.png",

      type: "image/png",

      disposition: "inline",

      contentId: "product-image",

    },

  ],

});


```

Explain Code

## Error handling

Handle email sending errors gracefully:

* [ Single send errors ](#tab-panel-6806)

TypeScript

```

export default {

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

    try {

      const response = await env.EMAIL.send({

        to: "user@example.com",

        from: "noreply@yourdomain.com",

        subject: "Test Email",

        text: "This is a test email.",

      });


      return new Response(

        JSON.stringify({

          success: true,

          emailId: response.messageId,

        }),

      );

    } catch (error) {

      // Error has .code and .message properties

      console.error("Email sending failed:", error.code, error.message);


      // Handle specific error types

      switch (error.code) {

        case "E_SENDER_NOT_VERIFIED":

          return new Response(

            JSON.stringify({

              success: false,

              error: "Please verify your sender domain first",

            }),

            { status: 400 },

          );


        case "E_RATE_LIMIT_EXCEEDED":

          return new Response(

            JSON.stringify({

              success: false,

              error: "Rate limit exceeded. Please try again later",

            }),

            { status: 429 },

          );


        default:

          return new Response(

            JSON.stringify({

              success: false,

              error: error.message,

            }),

            { status: 500 },

          );

      }

    }

  },

};


```

Explain Code

## Error codes

The following error codes may be returned when sending emails:

| Error Code                        | Description                             | Common Causes                                                                                                               |
| --------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| E\_VALIDATION\_ERROR              | Validation error in the payload         | Invalid email format, missing required fields, malformed data                                                               |
| E\_FIELD\_MISSING                 | Required field is missing               | Missing to, from, or subject fields                                                                                         |
| E\_TOO\_MANY\_RECIPIENTS          | Too many recipients in to/cc/bcc arrays | Combined recipients exceed 50 limit                                                                                         |
| E\_SENDER\_NOT\_VERIFIED          | Sender domain not verified              | Attempting to send from unverified domain                                                                                   |
| E\_RECIPIENT\_NOT\_ALLOWED        | Recipient not in allowed list           | Recipient address not in allowed\_destination\_addresses                                                                    |
| E\_RECIPIENT\_SUPPRESSED          | Recipient is on suppression list        | Email address has bounced or reported your emails as spam                                                                   |
| E\_SENDER\_DOMAIN\_NOT\_AVAILABLE | Domain not available for sending        | Domain not onboarded to Email Service                                                                                       |
| E\_CONTENT\_TOO\_LARGE            | Email content exceeds size limit        | Total message size exceeds the maximum                                                                                      |
| E\_DELIVERY\_FAILED               | Could not deliver the email             | SMTP delivery failure, recipient server rejection                                                                           |
| E\_RATE\_LIMIT\_EXCEEDED          | Rate limit exceeded                     | Sending rate limit reached                                                                                                  |
| E\_DAILY\_LIMIT\_EXCEEDED         | Daily limit exceeded                    | Daily sending quota reached                                                                                                 |
| E\_INTERNAL\_SERVER\_ERROR        | Internal service error                  | Email Service temporarily unavailable                                                                                       |
| E\_HEADER\_NOT\_ALLOWED           | Header not allowed                      | Header is platform-controlled or not on the [whitelist](https://developers.cloudflare.com/email-service/reference/headers/) |
| E\_HEADER\_USE\_API\_FIELD        | Must use API field                      | Header like From must be set via the dedicated API field                                                                    |
| E\_HEADER\_VALUE\_INVALID         | Header value invalid                    | Malformed value, empty, or incorrect format                                                                                 |
| E\_HEADER\_VALUE\_TOO\_LONG       | Header value too long                   | Value exceeds 2,048 byte limit                                                                                              |
| E\_HEADER\_NAME\_INVALID          | Header name invalid                     | Invalid characters or exceeds 100 byte limit                                                                                |
| E\_HEADERS\_TOO\_LARGE            | Headers payload too large               | Total custom headers exceed 16 KB limit                                                                                     |
| E\_HEADERS\_TOO\_MANY             | Too many headers                        | More than 20 whitelisted (non-X) custom headers                                                                             |

## Legacy `EmailMessage` API

The existing `EmailMessage` API remains supported for backward compatibility:

TypeScript

```

import { EmailMessage } from "cloudflare:email";

import { createMimeMessage } from "mimetext";


export default {

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

    const msg = createMimeMessage();

    msg.setSender({ name: "Sender", addr: "sender@yourdomain.com" });

    msg.setRecipient("recipient@example.com");

    msg.setSubject("Legacy Email");

    msg.addMessage({

      contentType: "text/html",

      data: "<h1>Hello from legacy API</h1>",

    });


    const message = new EmailMessage(

      "sender@yourdomain.com",

      "recipient@example.com",

      msg.asRaw(),

    );


    await env.EMAIL.send(message);

    return new Response("Legacy email sent");

  },

};


```

Explain Code

---

## Next steps

* See the [REST API](https://developers.cloudflare.com/email-service/api/send-emails/rest-api/) for sending emails without Workers
* See [practical examples](https://developers.cloudflare.com/email-service/examples/) of email sending patterns
* Learn about [email routing](https://developers.cloudflare.com/email-service/api/route-emails/) for handling incoming emails
* Explore [email authentication](https://developers.cloudflare.com/email-service/concepts/email-authentication/) for better deliverability

```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/api/","name":"API reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/api/send-emails/","name":"Send emails"}},{"@type":"ListItem","position":5,"item":{"@id":"/email-service/api/send-emails/workers-api/","name":"Workers API"}}]}
```

---

---
title: Email deliverability
description: When you send an email, there is no guarantee it reaches the recipient's inbox. Inbox providers like Gmail, Yahoo, Outlook, and iCloud invest heavily in filtering out unwanted email. If you send poorly targeted emails, have high bounce rates, or trigger spam complaints, these providers may flag your domain as untrustworthy. Once that happens, even your legitimate emails can end up in spam or be blocked outright.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/concepts/deliverability.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Email deliverability

Understand bounce handling and reputation management for optimal email delivery.

When you send an email, there is no guarantee it reaches the recipient's inbox. Inbox providers like Gmail, Yahoo, Outlook, and iCloud invest heavily in filtering out unwanted email. If you send poorly targeted emails, have high bounce rates, or trigger spam complaints, these providers may flag your domain as untrustworthy. Once that happens, even your legitimate emails can end up in spam or be blocked outright.

This concept is referred to as email deliverability: maintaining a healthy sending reputation so that inbox providers trust your emails. Cloudflare Email Service helps with this by automatically handling bounces, managing suppression lists, and authenticating your emails through SPF, DKIM, and DMARC.

## Bounces

Bounces occur when emails cannot be delivered to recipients. There are two types of bounces: **hard bounces** and **soft bounces**.

### Hard bounces

Hard bounces are permanent delivery failures that occur when:

* Email address doesn't exist (`user@domain.com` → No such user)
* Domain does not exist (`user@nonexistentdomain.com`)
* Recipient server permanently blocks your domain
* Content rejected as spam by recipient filters

**Hard bounces are never retried** because the failure is permanent. Emails that hard bounce will generate a bounce notification to the sender address and can be monitored through [analytics](https://developers.cloudflare.com/email-service/observability/metrics-analytics/).

Hard bounced addresses are automatically added to your [suppression list](https://developers.cloudflare.com/email-service/concepts/suppressions/) to protect your sender reputation.

### Soft bounces

Soft bounces are temporary failures that may succeed if retried:

* Recipient mailbox is full
* Email server temporarily down
* Rate limiting or greylisting

Cloudflare automatically retries soft bounces with exponential backoff over an extended period.

## Reputation management

Cloudflare automatically manages:

* **IP reputation**: Managed sending infrastructure optimized for deliverability
* **Domain authentication**: DKIM signing, SPF alignment, DMARC compliance
* **Feedback processing**: ISP complaint handling and suppression list management

### Best practices

#### Content and list hygiene

Avoid content that can trigger spam-detection or can be perceived as unwanted content:

* Avoid spam trigger words (FREE, URGENT, GUARANTEED)
* Include both HTML and plain text versions
* Use legitimate URLs and clear sender identification

Ensure that your email lists are clean and contain intended recipients:

* Validate emails before sending
* Implement double opt-in for subscriptions
* Remove hard bounced addresses immediately

Ensure that your deliverability stays above key metrics to avoid affecting your email sending reputation:

* Delivery rate >95%
* Hard bounce rate < 2%
* Complaint rate < 0.1%

#### Use separate domains for separate purposes

Each domain builds its own deliverability reputation with inbox providers. Use separate domains or subdomains for different types of email so that one category does not affect the reputation of another. For example:

* `notifications.yourdomain.com` for transactional emails (order confirmations, password resets)
* `marketing.yourdomain.com` for marketing and promotional emails
* `yourdomain.com` for important account-related communications

This way, if marketing emails generate higher complaint rates, your transactional email deliverability is not impacted. Each domain can be onboarded separately through [domain configuration](https://developers.cloudflare.com/email-service/configuration/domains/).

```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/concepts/","name":"Concepts"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/concepts/deliverability/","name":"Email deliverability"}}]}
```

---

---
title: Email authentication
description: Email authentication verifies sender identity and improves deliverability. Cloudflare Email Service handles authentication automatically, but understanding these concepts helps troubleshoot issues.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/concepts/email-authentication.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Email authentication

Learn about SPF, DKIM, and DMARC for secure and deliverable email sending.

Email authentication verifies sender identity and improves deliverability. **Cloudflare Email Service handles authentication automatically**, but understanding these concepts helps troubleshoot issues.

## SPF (Sender Policy Framework)

SPF ensures that no one else can send emails with your domain by authorizing which mail servers are allowed to send on your behalf.

Email Service configures separate SPF records for sending and routing:

* **Email Sending** SPF record on `cf-bounce.yourdomain.com`:  
```  
TXT cf-bounce.yourdomain.com "v=spf1 include:_spf.mx.cloudflare.net ~all"  
```
* **Email Routing** SPF record on the root domain:  
```  
TXT yourdomain.com "v=spf1 include:_spf.mx.cloudflare.net ~all"  
```

SPF works by:

1. Publishing authorized IP addresses in DNS
2. Recipient servers checking your SPF record
3. Comparing the sending IP against authorized IPs
4. Passing or failing based on the result

## DKIM (DomainKeys Identified Mail)

DKIM ensures that emails have not been tampered during transit by cryptographically signing them with your domain's private key.

**How DKIM works:**

1. Email headers and body are signed with a private key
2. DKIM-Signature header is added to the email
3. Public key is published in DNS
4. Recipients use the public key to verify the signature

Email Service uses separate DKIM selectors for sending and routing:

* **Email Sending**: `cf-bounce._domainkey.yourdomain.com`
* **Email Routing**: `cf2024-1._domainkey.yourdomain.com`

Cloudflare automatically generates and manages DKIM keys. You add the provided DNS records from the dashboard.

## DMARC (Domain-based Message Authentication, Reporting & Conformance)

DMARC ensures that emails claiming to be from your domain actually pass SPF and DKIM checks, telling recipients what to do with emails that fail authentication.

**DMARC record example:**

```

TXT _dmarc.yourdomain.com "v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com"


```

**DMARC policies:**

* `p=none` \- Monitor only (recommended to start)
* `p=quarantine` \- Quarantine suspicious emails
* `p=reject` \- Reject unauthenticated emails

**Deployment strategy:**

1. Start with `p=none` to monitor authentication
2. Gradually increase to `p=quarantine`
3. Finally implement `p=reject` after confirming legitimate mail authenticates

## Key benefits

Email authentication provides:

* **Deliverability**: Improves inbox placement
* **Security**: Protects your domain from spoofing
* **Reputation**: Maintains good sender reputation with ISPs

Cloudflare Email Service handles authentication automatically, but you need to configure the DNS records for SPF, DKIM, and DMARC as provided in your dashboard. Email Sending and Email Routing use separate DNS records -- refer to [Domain configuration](https://developers.cloudflare.com/email-service/configuration/domains/) for the full details.

```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/concepts/","name":"Concepts"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/concepts/email-authentication/","name":"Email authentication"}}]}
```

---

---
title: Email lifecycle
description: The email lifecycle describes the complete journey of an email from the initial send request through final delivery status. Understanding this process helps you optimize your email implementation and troubleshoot delivery issues.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/concepts/email-lifecycle.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Email lifecycle

Understand the complete email processing lifecycle from request received through final delivery status with Cloudflare Email Service

The email lifecycle describes the complete journey of an email from the initial send request through final delivery status. Understanding this process helps you optimize your email implementation and troubleshoot delivery issues.

## Email processing flow

Every email sent through Cloudflare Email Service follows this processing pipeline:

flowchart LR
    A[Request Received] --> B["Rate Limit, Authentication & Suppression Check"] --> E[Delivery Attempt]
    E --> G{Success?}
    G -->|Yes, successfully delivered| F[Final Status & Metrics]
    G -->|No - Soft Bounce| H[Retry with Exponential Backoff]
    G -->|No - Hard Bounce| F
    H --> E
    H -->|Max retries exceeded| F

### Stage Details:

1. **Request received:** The system validates the email format, sender authorization, and message structure. Invalid requests are rejected immediately and do not proceed to the next stage.
2. **Rate limit check:** The system checks sending [limits](https://developers.cloudflare.com/email-service/platform/limits) per account, domain, and recipient to prevent abuse. Requests that exceed these limits are temporarily rejected and must be retried later.
3. **Authentication and reputation**: The system performs email authentication checks and evaluates sender reputation:  
   * **SPF (Sender Policy Framework)**: Verifies that the sending IP address is authorized to send emails for the domain by checking DNS TXT records. This prevents domain spoofing and improves deliverability.  
   * **DKIM (DomainKeys Identified Mail)**: Validates the email's cryptographic signature to ensure message integrity and authenticate the sender domain. This builds trust with recipient servers.  
   * **DMARC (Domain-based Message Authentication)**: Applies domain owner policies for handling emails that fail SPF or DKIM checks, helping prevent phishing and brand impersonation while providing feedback reports.  
These authentication mechanisms work together to establish sender legitimacy and protect against email fraud. Senders with low reputation scores may experience throttling or delayed processing.
4. **Suppression list check:** The system checks the recipient against your account's suppression list, which includes bounces, complaints, and unsubscribes. Recipients found on this list are blocked from receiving the email.
5. **Delivery attempt:** The system connects to the recipient's mail server and attempts message delivery via SMTP. When delivery fails, the system applies different retry logic based on the failure type:  
   * **Soft bounces (4xx responses)**: The system retries delivery using exponential backoff timing  
   * **Hard bounces (5xx responses)**: The system marks the email as permanently failed with no retry attempts
6. **Server response handling:** The system processes SMTP response codes from the recipient server to determine the final email status:  
   * **2xx codes**: The email was delivered successfully  
   * **4xx codes**: Temporary failure occurred and the email will be retried  
   * **5xx codes**: Permanent failure occurred and the email cannot be delivered
7. **Final status and metrics:** Based on the server response, the system assigns emails one of these final statuses:  
   * **Delivered**: The email was successfully accepted by the recipient server  
   * **Delivery failed**: The email permanently failed delivery (hard bounce) or exceeded the maximum retry attempts (soft bounce). This status appears as `deliveryFailed` when querying the [GraphQL Analytics API](https://developers.cloudflare.com/email-service/observability/metrics-analytics/).

Understanding the email lifecycle helps you build robust email applications that handle all possible outcomes and provide excellent user experiences through proper status tracking and error handling.

```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/concepts/","name":"Concepts"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/concepts/email-lifecycle/","name":"Email lifecycle"}}]}
```

---

---
title: Suppressions lists
description: Suppression lists prevent emails from being sent to addresses that should not receive them, protecting your sender reputation and ensuring compliance with anti-spam regulations.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/concepts/suppressions.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Suppressions lists

Manage email suppression lists to prevent emails from being sent to addresses that shouldn't receive them, protecting your sender reputation with automatic and manual suppression management.

Suppression lists prevent emails from being sent to addresses that should not receive them, protecting your sender reputation and ensuring compliance with anti-spam regulations.

## Account suppression list

Cloudflare automatically manages suppressions for your account to preserve your reputation as an email sender.

Cloudflare will automatically add email addresses to your account suppression list for the following reasons:

* **Hard bounces**: Invalid or non-existent email addresses are immediately suppressed.
* **Repeated soft bounces**: Addresses that repeatedly fail delivery are temporarily or permanently suppressed based on the frequency and pattern of failures.
* **Spam complaints**: Recipients who marked emails as spam. Cloudflare integrates with Postmasters to receive spam complaints and automatically updates your account suppression list to prevent you from sending emails to this email address and preserve your email sending reputation.

You may also manually add or remove email addresses from your suppression list as needed. The removal of email addresses that have been automatically added to your suppression list as a result of a spam complaint is limited to avoid abuse.

## Best practices

### List hygiene

Maintaining clean suppression lists is essential for optimal email delivery performance and sender reputation. Regular maintenance helps identify delivery issues early and ensures legitimate recipients can receive your emails.

* Review suppression lists monthly
* Remove temporary suppressions that have expired
* Identify patterns in suppressed addresses
* Update email validation rules based on common issues

```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/concepts/","name":"Concepts"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/concepts/suppressions/","name":"Suppressions lists"}}]}
```

---

---
title: Domain configuration
description: Configure your domains to work with Cloudflare Email Service. This includes DNS record management, domain verification, and advanced domain settings.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/configuration/domains.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Domain configuration

Configure domains for Cloudflare Email Service, manage DNS records, and verify domain setup for both email sending and routing.

Configure your domains to work with Cloudflare Email Service. This includes DNS record management, domain verification, and advanced domain settings.

## Automatic DNS configuration

You can quickly get your DNS configured by following the automatic DNS configuration flow as part of the onboarding onto Email Service.

1. Log in to the [Cloudflare Dashboard ↗](https://dash.cloudflare.com).
2. Navigate to **Compute** \> **Email Service** \> **Email Sending** or **Email Routing**.
3. Select **Onboard Domain**.
4. Choose a domain from your Cloudflare account.
5. Select **Next** to configure DNS records.
6. Press **Add records and onboard**. This will add the following DNS records to your domain:  
   * TXT records for SPF to authorize sending emails and routing forwarded emails.  
   * TXT records for DKIM to provide authentication for emails sent and forwarded from your domain.  
   * MX records to route incoming emails to Email Service.

## DNS record configuration details

Cloudflare automatically configures required DNS records for both email sending and routing when you onboard a domain onto Email Service. Here are the specific details of the DNS records configured:

### Sending records

These records authenticate your outbound emails. Email Sending creates DNS records on a `cf-bounce.` subdomain of your domain to handle bounce processing. These are separate from the records used by Email Routing.

* [ MX Records ](#tab-panel-6814)
* [ SPF Record ](#tab-panel-6815)
* [ DKIM Record ](#tab-panel-6816)
* [ DMARC Record ](#tab-panel-6817)

**Purpose**: Route bounce emails back to Cloudflare for processing.

```

MX cf-bounce.yourdomain.com route1.mx.cloudflare.net

MX cf-bounce.yourdomain.com route2.mx.cloudflare.net

MX cf-bounce.yourdomain.com route3.mx.cloudflare.net


```

**Configuration:**

* **Type**: MX
* **Name**: `cf-bounce` (subdomain)
* **Mail server**: Cloudflare MX servers
* **Priority**: Assigned automatically by Cloudflare

**Purpose**: Authorizes Cloudflare to send emails on behalf of your domain.

```

TXT cf-bounce.yourdomain.com "v=spf1 include:_spf.mx.cloudflare.net ~all"


```

**Configuration:**

* **Type**: TXT
* **Name**: `cf-bounce` (subdomain)
* **Value**: `v=spf1 include:_spf.mx.cloudflare.net ~all`
* **TTL**: Auto

**Purpose**: Provides cryptographic authentication for your emails.

```

TXT cf-bounce._domainkey.yourdomain.com "v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."


```

**Configuration:**

* **Type**: TXT
* **Name**: `cf-bounce._domainkey` (selector managed by Cloudflare)
* **Value**: DKIM public key (provided by Cloudflare)
* **TTL**: Auto

**Purpose**: Sets policy for email authentication failures.

```

TXT _dmarc.yourdomain.com "v=DMARC1; p=reject;"


```

**Configuration:**

* **Type**: TXT
* **Name**: `_dmarc`
* **Value**: DMARC policy
* **TTL**: Auto

**Policy options:**

* `p=none` \- Monitor only (recommended for new setups)
* `p=quarantine` \- Quarantine suspicious emails
* `p=reject` \- Reject unauthenticated emails

### Routing records

These records route incoming emails to Cloudflare and authenticate forwarded emails. Email Routing DNS records are configured on the root domain.

* [ MX Records ](#tab-panel-6818)
* [ SPF Record ](#tab-panel-6819)
* [ DKIM Record ](#tab-panel-6820)

**Purpose**: Route incoming emails to Cloudflare's mail servers.

```

MX yourdomain.com route1.mx.cloudflare.net

MX yourdomain.com route2.mx.cloudflare.net

MX yourdomain.com route3.mx.cloudflare.net


```

**Configuration:**

* **Type**: MX
* **Name**: `@` (root domain)
* **Mail server**: Cloudflare routing MX servers
* **Priority**: Assigned automatically by Cloudflare

**Purpose**: Authorizes Cloudflare to forward emails on behalf of your domain.

```

TXT yourdomain.com "v=spf1 include:_spf.mx.cloudflare.net ~all"


```

**Configuration:**

* **Type**: TXT
* **Name**: `@` (root domain)
* **Value**: `v=spf1 include:_spf.mx.cloudflare.net ~all`
* **TTL**: Auto

Existing SPF Records

If you have existing SPF records, merge them: `v=spf1 include:_spf.mx.cloudflare.net include:_spf.google.com ~all`

**Purpose**: Provides cryptographic authentication for forwarded emails.

```

TXT cf2024-1._domainkey.yourdomain.com "v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."


```

**Configuration:**

* **Type**: TXT
* **Name**: `cf2024-1._domainkey` (selector provided by Cloudflare)
* **Value**: DKIM public key (provided by Cloudflare)
* **TTL**: Auto

**Separate from sending DKIM** \- Email Routing uses its own DKIM selector (`cf2024-1._domainkey`) and keys, distinct from the sending DKIM selector (`cf-bounce._domainkey`).

## Domain verification

Email Sending and Email Routing have separate DNS records and separate settings pages where you can verify their status.

### Verify Email Sending records

1. Go to **Compute** \> **Email Service** \> **Email Sending** \> **Settings**.
2. The **DNS records** section shows all sending-related records:  
   * **MX records** on `cf-bounce.yourdomain.com`  
   * **SPF record** on `cf-bounce.yourdomain.com`  
   * **DKIM record** on `cf-bounce._domainkey.yourdomain.com`  
   * **DMARC record** on `_dmarc.yourdomain.com`
3. Each record shows a **Locked** status when properly configured.

### Verify Email Routing records

1. Go to **Compute** \> **Email Service** \> **Email Routing** \> **Settings**.
2. The **DNS records** section shows all routing-related records:  
   * **MX records** on `yourdomain.com`  
   * **SPF record** on `yourdomain.com`  
   * **DKIM record** on `cf2024-1._domainkey.yourdomain.com`
3. Each record shows a **Locked** status when properly configured.

### If records are not configured

* Wait 5-15 minutes for DNS propagation.
* Check DNS configuration in your domain's **DNS** \> **Records** settings.

### Verification troubleshooting

* [ DNS Propagation ](#tab-panel-6821)
* [ Record Conflicts ](#tab-panel-6822)

**Issue**: Records show as "Not Found" immediately after adding.

**Solution**:

* Wait 5-15 minutes for DNS propagation
* Check propagation status: `dig TXT yourdomain.com`
* Cloudflare domains propagate faster than external domains

**Check propagation globally:**

Terminal window

```

# Check sending SPF record

dig TXT cf-bounce.yourdomain.com | grep spf


# Check routing SPF record

dig TXT yourdomain.com | grep spf


# Check sending DKIM record

dig TXT cf-bounce._domainkey.yourdomain.com


# Check routing DKIM record

dig TXT cf2024-1._domainkey.yourdomain.com


# Check routing MX records

dig MX yourdomain.com


# Check sending MX records (bounce handling)

dig MX cf-bounce.yourdomain.com


```

Explain Code

**Issue**: Existing DNS records conflict with Email Service.

**SPF Conflicts:**

* Merge existing SPF records
* Remove duplicate `v=spf1` entries
* Ensure only one SPF record exists

**MX Conflicts:**

* Email Routing requires Cloudflare MX records
* Remove or update existing MX records
* Cannot use Email Routing with external mail servers

**DKIM Conflicts:**

* Use different selectors for different services
* `cf-bounce._domainkey` for Email Sending
* `cf2024-1._domainkey` for Email Routing
* `google._domainkey` for Google Workspace

## Domain management

### Remove a domain

1. Go to **Compute** \> **Email Service** \> **Email Sending** \> **Settings**.
2. Select the domain to remove.
3. Select **Remove Domain**.
4. Confirm removal.

Domain Removal Impact

Removing a domain will:

* Stop all email sending from that domain
* Disable email routing for that domain
* Require reconfiguration if re-added

### DNS record management

When you remove a domain from Email Service, you have two options for handling the DNS records:

**Option 1: Remove all records**

This removes all Email Service DNS records from your domain:

* All SPF, DKIM, and MX records for Email Service are deleted
* Your domain will no longer receive or send emails through Email Service
* If you want to use Email Service again in the future, you will need to onboard the domain and add all records from scratch

**Option 2: Keep records**

This keeps the DNS records in place but disables Email Service:

* DNS records remain in your domain configuration
* Email Service stops processing emails for the domain
* You can re-enable Email Service by onboarding the domain again
* DNS records that were automatically added will remain locked to prevent accidental deletion

To modify locked records after removal:

1. Go to your domain's **DNS** \> **Records**.
2. Find the locked Email Service records.
3. Select the record and choose **Edit**.
4. Toggle **Unlock record** to enable editing.
5. Make your changes and save.

Note

Keeping records is useful if you plan to re-enable Email Service later. Removing records is recommended if you are migrating to a different email provider.

### Transfer domain ownership

1. Domain must remain in the same Cloudflare account.
2. DNS records are tied to the account, not specific users.
3. Use Cloudflare account-level permissions to manage access.

## Next steps

* **[Send emails API](https://developers.cloudflare.com/email-service/api/send-emails/)**: Workers binding and REST API reference
* **[Domain authentication (DKIM and SPF)](https://developers.cloudflare.com/email-service/concepts/email-authentication/)**: Learn about SPF, DKIM, and DMARC
* **[Deliverability](https://developers.cloudflare.com/email-service/concepts/deliverability/)**: Optimize email delivery

```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/configuration/domains/","name":"Domain configuration"}}]}
```

---

---
title: Email routing rules and addresses
description: In Email routing, an email rule is a pair of a custom email address and a destination address, or a custom email address and an associated Worker. You can route emails to either:
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/configuration/email-routing-addresses.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Email routing rules and addresses

In Email routing, an email rule is a pair of a custom email address and a destination address, or a custom email address and an associated Worker. You can route emails to either:

* Verified email addresses
* Workers with the `email` handler

This allows you to route emails to your preferred inbox, or apply logic with Workers before deciding what should happen to your emails. You can have multiple custom addresses, to route email from specific providers to specific mail inboxes.

## Custom addresses

1. Log in to the [Cloudflare dashboard ↗](https://dash.cloudflare.com/) and select your account and domain.
2. Go to **Compute** \> **Email Service** \> **Email Routing** \> **Routing Rules**.  
[ Go to **Email Routing** ](https://dash.cloudflare.com/?to=/:account/email-service/routing)
3. Select **Create address**.
4. In **Custom address**, enter the custom email address you want to use (for example, `my-new-email`).
5. In the **Action** drop-down menu, choose what this email rule should do. Refer to [Email rule actions](#email-rule-actions) for more information.
6. In **Destination**, choose the email address or Worker you want your emails to be forwarded to — for example, `your-name@gmail.com`. You can only choose a destination address you have already verified. To add a new destination address, refer to [Destination addresses](#destination-addresses).

Note

If you have more than one destination address linked to the same custom address, Email Routing will only process the most recent rule. This means only the most recent pair of custom address and destination address (rule) will receive your forwarded emails. To avoid this, do not link more than one destination address to the same custom address.

### Email rule actions

When creating an email rule, you must specify an **Action**:

* _Send to an email_: Emails will be routed to your destination address. This is the default action.
* _Send to a Worker_: Emails will be processed by the logic in your [Worker](https://developers.cloudflare.com/email-service/api/route-emails/email-handler/).
* _Drop_: Deletes emails sent to the custom address without routing them. This can be useful if you want to make an email address appear valid for privacy reasons.

Note

To prevent spamming unintended recipients, all email rules are automatically disabled until the destination address is validated by the user.

### Disable an email rule

1. In the Cloudflare dashboard, go to **Email Routing**.  
[ Go to **Email Routing** ](https://dash.cloudflare.com/?to=/:account/email-service/routing)
2. Select **Routes**.
3. In **Custom addresses**, identify the email rule you want to pause, and toggle the status button to **Disabled**.

Your email rule is now disabled. It will not forward emails to a destination address or Worker. To forward emails again, toggle the email rule status button to **Active**.

### Edit custom addresses

1. In the Cloudflare dashboard, go to **Email Routing**.  
[ Go to **Email Routing** ](https://dash.cloudflare.com/?to=/:account/email-service/routing)
2. Select **Routes**.
3. In **Custom addresses**, identify the email rule you want to edit, and select **Edit**.
4. Make the appropriate changes to this custom address.

### Delete a custom address

1. Log in to the [Cloudflare dashboard ↗](https://dash.cloudflare.com/) and select your account and domain.
2. Go to **Compute** \> **Email Service** \> **Email Routing** \> **Routing Rules**.
3. In **Custom addresses**, identify the email rule you want to delete.
4. Select **Delete** and confirm the action.

Deleting a custom address will permanently remove the routing rule. Emails sent to this address will no longer be routed. If you want to temporarily stop routing without deleting the rule, refer to [Disable an email rule](#disable-an-email-rule).

## Catch-all address

When you enable this feature, Email Routing will catch variations of email addresses to make them valid for the specified domain. For example, if you created an email rule for `info@example.com` and a sender accidentally types `ifno@example.com`, the email will still be correctly handled if you have **Catch-all addresses** enabled.

To enable Catch-all addresses:

1. In the Cloudflare dashboard, go to **Email Routing**.  
[ Go to **Email Routing** ](https://dash.cloudflare.com/?to=/:account/email-service/routing)
2. Select **Routes**.
3. Enable **Catch-all address**, so it shows as **Active**.
4. In the **Action** drop-down menu, select what to do with these emails. Refer to [Email rule actions](#email-rule-actions) for more information.
5. Select **Save**.

## Subaddressing

Email Routing supports subaddressing, also known as plus addressing, as defined in [RFC 5233 ↗](https://www.rfc-editor.org/rfc/rfc5233). This enables using the "+" separator to augment your custom addresses with arbitrary detail information.

You can enable subaddressing at **Email** \> **Email Routing** \> **Settings**.

Once enabled, you can use subaddressing with any of your custom addresses. For example, if you send an email to `user+detail@example.com` it will be captured by the `user@example.com` custom address. The `+detail` part is ignored by Email Routing, but it can be captured next in the processing chain in the logs, a [Worker](https://developers.cloudflare.com/email-service/api/route-emails/email-handler/) or an [Agent application ↗](https://github.com/cloudflare/agents/tree/main/examples/email-agent).

If a custom address `user+detail@example.com` already exists, it will take precedence over `user@example.com`. This prevents breaking existing routing rules for users, and allows certain sub-addresses to be captured by a specific rule.

## Destination addresses

This section lets you manage your destination addresses. It lists all email addresses already verified, as well as email addresses pending verification. You can resend verification emails or delete destination addresses.

Destination addresses are shared at the account level, and can be reused with any other domain in your account. This means the same destination address will be available to different domains in your account.

To prevent spam, email rules do not become active until after the destination address has been verified. Cloudflare sends a verification email to destination addresses specified in **Custom addresses**. You have to select **Verify email address** in that email to activate a destination address.

Note

Deleting a destination address automatically disables all email rules that use that email address as destination.

```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/configuration/email-routing-addresses/","name":"Email routing rules and addresses"}}]}
```

---

---
title: Configure MTA-STS
description: MTA Strict Transport Security (MTA-STS) was introduced by email service providers including Microsoft, Google and Yahoo as a solution to protect against downgrade and man-in-the-middle attacks in SMTP sessions, as well as solving the lack of security-first communication standards in email.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/configuration/mta-sts.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Configure MTA-STS

MTA Strict Transport Security ([MTA-STS ↗](https://datatracker.ietf.org/doc/html/rfc8461)) was introduced by email service providers including Microsoft, Google and Yahoo as a solution to protect against downgrade and man-in-the-middle attacks in SMTP sessions, as well as solving the lack of security-first communication standards in email.

Suppose that `example.com` is your domain and uses Email Service. Here is how you can enable MTA-STS for it.

1. In the Cloudflare dashboard, go to the **Records** page.  
[ Go to **Records** ](https://dash.cloudflare.com/?to=/:account/:zone/dns/records)
2. Create a new CNAME record with the name `_mta-sts` that points to Cloudflare’s record `_mta-sts.mx.cloudflare.net`. Make sure to disable the proxy mode.
![MTA-STS CNAME record](https://developers.cloudflare.com/_astro/mta-sts-record.DbwO-t_X_1Mbxza.webp) 
1. Confirm that the record was created:

Terminal window

```

dig txt _mta-sts.example.com


```

```

_mta-sts.example.com. 300 IN  CNAME _mta-sts.mx.cloudflare.net.

_mta-sts.mx.cloudflare.net. 300 IN  TXT "v=STSv1; id=20230615T153000;"


```

This tells the other end client that is trying to connect to us that we support MTA-STS.

Next you need an HTTPS endpoint at `mta-sts.example.com` to serve your policy file. This file defines the mail servers in the domain that use MTA-STS. The reason why HTTPS is used here instead of DNS is because not everyone uses DNSSEC yet, so we want to avoid another MITM attack vector.

To do this you need to deploy a Worker that allows email clients to pull Cloudflare’s Email Service policy file using the “well-known” URI convention.

1. Go to your **Account** \> **Workers & Pages** and select **Create**. Pick the default "Hello World" option button, and replace the sample worker code with the following:

JavaScript

```

export default {

  async fetch(request, env, ctx) {

    return await fetch(

      "https://mta-sts.mx.cloudflare.net/.well-known/mta-sts.txt",

    );

  },

};


```

This Worker proxies `https://mta-sts.mx.cloudflare.net/.well-known/mta-sts.txt` to your own domain.

1. After deploying it, go to the Worker configuration, then **Settings** \> **Domains & Routes** \> **+Add**. Type the subdomain `mta-sts.example.com`.
![MTA-STS Worker Custom Domain](https://developers.cloudflare.com/_astro/mta-sts-domain.UfZmAoBe_lkXVJ.webp) 

You can then confirm that your policy file is working with the following:

Terminal window

```

curl https://mta-sts.example.com/.well-known/mta-sts.txt


```

```

version: STSv1

mode: enforce

mx: *.mx.cloudflare.net

max_age: 86400


```

This says that you domain `example.com` enforces MTA-STS. Capable email clients will only deliver email to this domain over a secure connection to the specified MX servers. If no secure connection can be established the email will not be delivered.

Email Service also supports MTA-STS upstream, which greatly improves security when forwarding your emails to service providers like Gmail, Microsoft, and others.

---

For more information about domain security and email authentication, refer to [Email authentication](https://developers.cloudflare.com/email-service/concepts/email-authentication/).

```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/configuration/","name":"Configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/configuration/mta-sts/","name":"Configure MTA-STS"}}]}
```

---

---
title: Email storage and processing
description: Store and process incoming emails with comprehensive storage, queue processing, and support ticket automation for streamlined email workflow management.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/examples/email-routing/email-storage.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Email storage and processing

Store and process incoming emails using KV storage and queue systems for support tickets and workflow automation

Store and process incoming emails with comprehensive storage, queue processing, and support ticket automation for streamlined email workflow management.

## Store emails in KV

Store emails in KV namespace for later processing:

TypeScript

```

interface Env {

  EMAIL: SendEmail;

  EMAILS: KVNamespace;

  SUPPORT_TICKETS: KVNamespace;

}


export default {

  async email(message, env, ctx): Promise<void> {

    const emailId = `email-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;


    // Read email content

    const reader = message.raw.getReader();

    const chunks = [];


    try {

      while (true) {

        const { done, value } = await reader.read();

        if (done) break;

        chunks.push(value);

      }


      const decoder = new TextDecoder();

      const rawContent = decoder.decode(

        new Uint8Array(chunks.reduce((acc, chunk) => [...acc, ...chunk], [])),

      );


      // Store email metadata and content

      const emailData = {

        id: emailId,

        from: message.from,

        to: message.to,

        subject: message.headers.get("subject"),

        timestamp: new Date().toISOString(),

        size: message.rawSize,

        rawContent: rawContent,

        processed: false,

      };


      await env.EMAILS.put(emailId, JSON.stringify(emailData));


      // Process based on recipient

      if (message.to.includes("support@")) {

        await handleSupportEmail(message, env, emailId);

      } else {

        await message.forward("general@company.com");

      }

    } finally {

      reader.releaseLock();

    }

  },

};


async function handleSupportEmail(message, env, emailId) {

  const ticketId = `TICKET-${Date.now()}`;


  // Create support ticket

  const ticketData = {

    id: ticketId,

    emailId: emailId,

    from: message.from,

    subject: message.headers.get("subject"),

    status: "open",

    priority: "normal",

    createdAt: new Date().toISOString(),

    updatedAt: new Date().toISOString(),

  };


  await env.SUPPORT_TICKETS.put(ticketId, JSON.stringify(ticketData));


  // Send auto-reply with ticket number

  await env.EMAIL.send({

    to: message.from,

    from: "support@company.com",

    subject: `Support Ticket Created: ${ticketId}`,

    html: `

            <h1>Support Ticket Created</h1>

            <p>Your support request has been received and assigned ticket number: <strong>${ticketId}</strong></p>

            <p>We will respond within 2-4 hours during business hours.</p>

            <hr>

            <p><em>Original subject: ${message.headers.get("subject")}</em></p>

        `,

  });


  // Forward to support team

  await message.forward("support-team@company.com");

}


```

Explain Code

## Queue-based processing

Process emails asynchronously using Cloudflare Queues:

TypeScript

```

interface Env {

  EMAIL: SendEmail;

  EMAIL_QUEUE: Queue;

  EMAIL_STORAGE: KVNamespace;

  EMAIL_ANALYTICS: AnalyticsEngine;

}


interface EmailQueueMessage {

  emailId: string;

  from: string;

  to: string;

  subject: string;

  timestamp: string;

  priority: "low" | "normal" | "high" | "urgent";

  category: string;

}


export default {

  // Handle incoming emails

  async email(message, env, ctx): Promise<void> {

    const emailId = `email-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;


    // Store raw email content

    const reader = message.raw.getReader();

    const chunks = [];


    try {

      while (true) {

        const { done, value } = await reader.read();

        if (done) break;

        chunks.push(value);

      }


      const decoder = new TextDecoder();

      const rawContent = decoder.decode(

        new Uint8Array(chunks.reduce((acc, chunk) => [...acc, ...chunk], [])),

      );


      // Store email with metadata

      const emailData = {

        id: emailId,

        from: message.from,

        to: message.to,

        subject: message.headers.get("subject"),

        timestamp: new Date().toISOString(),

        size: message.rawSize,

        rawContent: rawContent,

        processed: false,

        status: "queued",

      };


      await env.EMAIL_STORAGE.put(emailId, JSON.stringify(emailData));


      // Determine priority and category

      const priority = determinePriority(message);

      const category = determineCategory(message);


      // Queue email for processing

      const queueMessage: EmailQueueMessage = {

        emailId,

        from: message.from,

        to: message.to,

        subject: message.headers.get("subject") || "",

        timestamp: new Date().toISOString(),

        priority,

        category,

      };


      await env.EMAIL_QUEUE.send(queueMessage, {

        delaySeconds: priority === "urgent" ? 0 : priority === "high" ? 5 : 30,

      });


      // Send immediate auto-reply

      await env.EMAIL.send({

        to: message.from,

        from: message.to,

        subject: `Re: ${message.headers.get("subject")}`,

        text: "Thank you for your message. It has been queued for processing.",

      });

    } finally {

      reader.releaseLock();

    }

  },


  // Process queued emails

  async queue(batch, env, ctx): Promise<void> {

    console.log(`📥 Processing ${batch.messages.length} queued emails`);


    for (const message of batch.messages) {

      try {

        const emailData = message.body as EmailQueueMessage;


        console.log(

          `📧 Processing ${emailData.category} email from ${emailData.from}`,

        );


        // Get stored email content

        const storedEmailData = await env.EMAIL_STORAGE.get(emailData.emailId);

        if (!storedEmailData) {

          console.error(`Email data not found: ${emailData.emailId}`);

          message.ack();

          continue;

        }


        const emailContent = JSON.parse(storedEmailData);


        // Process based on category

        let processResult;

        switch (emailData.category) {

          case "support":

            processResult = await processSupport(emailData, emailContent, env);

            break;

          case "sales":

            processResult = await processSales(emailData, emailContent, env);

            break;

          case "billing":

            processResult = await processBilling(emailData, emailContent, env);

            break;

          default:

            processResult = await processGeneral(emailData, emailContent, env);

        }


        // Update email status

        emailContent.processed = true;

        emailContent.status = "completed";

        emailContent.processedAt = new Date().toISOString();

        emailContent.processingResult = processResult;


        await env.EMAIL_STORAGE.put(

          emailData.emailId,

          JSON.stringify(emailContent),

        );


        // Track processing metrics

        env.EMAIL_ANALYTICS?.writeDataPoint({

          blobs: [

            "email_processed",

            emailData.from,

            emailData.to,

            emailData.category,

            emailData.priority,

          ],

          doubles: [1, emailContent.size],

          indexes: [

            `category:${emailData.category}`,

            `priority:${emailData.priority}`,

          ],

        });


        message.ack();

      } catch (error) {

        console.error("Failed to process email:", error);

        message.retry();

      }

    }

  },

};


function determinePriority(message): "low" | "normal" | "high" | "urgent" {

  const subject = (message.headers.get("subject") || "").toLowerCase();

  const to = message.to.toLowerCase();


  if (subject.includes("urgent") || subject.includes("emergency")) {

    return "urgent";

  }


  if (

    to.includes("support") &&

    (subject.includes("down") || subject.includes("error"))

  ) {

    return "high";

  }


  if (to.includes("sales") || to.includes("billing")) {

    return "high";

  }


  return "normal";

}


function determineCategory(message): string {

  const to = message.to.toLowerCase();

  const subject = (message.headers.get("subject") || "").toLowerCase();


  if (

    to.includes("support") ||

    subject.includes("help") ||

    subject.includes("issue")

  ) {

    return "support";

  }


  if (

    to.includes("sales") ||

    subject.includes("quote") ||

    subject.includes("pricing")

  ) {

    return "sales";

  }


  if (

    to.includes("billing") ||

    subject.includes("invoice") ||

    subject.includes("payment")

  ) {

    return "billing";

  }


  return "general";

}


async function processSupport(

  emailData: EmailQueueMessage,

  emailContent: any,

  env: Env,

) {

  const ticketId = `TICKET-${Date.now()}`;


  // Create support ticket

  const ticketData = {

    id: ticketId,

    emailId: emailData.emailId,

    from: emailData.from,

    subject: emailData.subject,

    priority: emailData.priority,

    status: "open",

    category: "support",

    createdAt: new Date().toISOString(),

    updatedAt: new Date().toISOString(),

    content: emailContent.rawContent.substring(0, 5000), // Limit stored content

  };


  await env.SUPPORT_TICKETS?.put(ticketId, JSON.stringify(ticketData));


  // Send confirmation email

  await env.EMAIL.send({

    to: emailData.from,

    from: "support@company.com",

    subject: `Support Ticket Created: ${ticketId}`,

    html: `

      <h2>Support Ticket Created</h2>

      <p>Your support request has been received and assigned ticket number: <strong>${ticketId}</strong></p>

      <p><strong>Priority:</strong> ${emailData.priority}</p>

      <p>We will respond based on the priority level:</p>

      <ul>

        <li><strong>Urgent:</strong> Within 1 hour</li>

        <li><strong>High:</strong> Within 4 hours</li>

        <li><strong>Normal:</strong> Within 24 hours</li>

      </ul>

      <hr>

      <p><em>Original subject: ${emailData.subject}</em></p>

    `,

  });


  return { ticketId, action: "ticket_created" };

}


async function processSales(

  emailData: EmailQueueMessage,

  emailContent: any,

  env: Env,

) {

  // Create sales lead

  const leadId = `LEAD-${Date.now()}`;


  const leadData = {

    id: leadId,

    emailId: emailData.emailId,

    contact: emailData.from,

    subject: emailData.subject,

    priority: emailData.priority,

    status: "new",

    source: "email",

    createdAt: new Date().toISOString(),

  };


  await env.SALES_LEADS?.put(leadId, JSON.stringify(leadData));


  // Send sales response

  await env.EMAIL.send({

    to: emailData.from,

    from: "sales@company.com",

    subject: `Re: ${emailData.subject}`,

    html: `

      <h2>Thank you for your interest!</h2>

      <p>We've received your sales inquiry and assigned it reference: <strong>${leadId}</strong></p>

      <p>A member of our sales team will contact you within 24 hours.</p>

      <p>Best regards,<br>Sales Team</p>

    `,

  });


  return { leadId, action: "lead_created" };

}


async function processBilling(

  emailData: EmailQueueMessage,

  emailContent: any,

  env: Env,

) {

  // Handle billing inquiries

  await env.EMAIL.send({

    to: emailData.from,

    from: "billing@company.com",

    subject: `Re: ${emailData.subject}`,

    html: `

      <h2>Billing Inquiry Received</h2>

      <p>Thank you for contacting our billing department.</p>

      <p>Your inquiry has been forwarded to our billing specialists who will respond within 2 business hours.</p>

      <p>For immediate assistance, please call: +1-800-555-0123</p>

    `,

  });


  return { action: "billing_forwarded" };

}


async function processGeneral(

  emailData: EmailQueueMessage,

  emailContent: any,

  env: Env,

) {

  // Handle general inquiries

  await env.EMAIL.send({

    to: emailData.from,

    from: "info@company.com",

    subject: `Re: ${emailData.subject}`,

    text: `

      Thank you for contacting us.


      We have received your message and will respond within 48 hours.


      For urgent matters, please contact our support team at support@company.com.


      Best regards,

      Customer Service Team

    `,

  });


  return { action: "general_acknowledged" };

}


```

Explain Code

---

This email storage system provides comprehensive email processing with KV storage, queue-based processing, and automated responses for different email categories.

```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-routing/","name":"Email routing"}},{"@type":"ListItem","position":5,"item":{"@id":"/email-service/examples/email-routing/email-storage/","name":"Email storage and processing"}}]}
```

---

---
title: Handle hard bounce emails
description: Handle hard bounce notifications to automatically remove invalid email addresses from your mailing lists and maintain good sender reputation.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/examples/email-routing/hard-bounce-handling.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Handle hard bounce emails

Detect and handle hard bounce emails to maintain sender reputation and manage undeliverable addresses

Handle hard bounce notifications to automatically remove invalid email addresses from your mailing lists and maintain good sender reputation.

## What are hard bounces?

Hard bounces occur when an email cannot be delivered due to permanent reasons:

* Invalid email address: The email address does not exist
* Domain does not exist: The domain name is invalid or expired
* Mailbox full: The recipient's mailbox has exceeded storage limits
* Email blocked: The recipient's server permanently rejects emails

## Configuration

Configure your worker to handle bounce notifications:

* [  wrangler.jsonc ](#tab-panel-6823)
* [  wrangler.toml ](#tab-panel-6824)

JSONC

```

{

  "$schema": "./node_modules/wrangler/config-schema.json",

  "name": "bounce-handler",

  // Set this to today's date

  "compatibility_date": "2026-04-17",

  "send_email": [

    {

      "name": "EMAIL"

    }

  ],

  "kv_namespaces": [

    {

      "binding": "SUPPRESSION_LIST",

      "id": "your-kv-namespace-id"

    }

  ]

}


```

Explain Code

TOML

```

name = "bounce-handler"

# Set this to today's date

compatibility_date = "2026-04-17"


[[send_email]]

name = "EMAIL"


[[kv_namespaces]]

binding = "SUPPRESSION_LIST"

id = "your-kv-namespace-id"


```

Explain Code

## Hard bounce detection

JavaScript

```

import * as PostalMime from 'postal-mime';


export default {

  async email(message, env, ctx) {

    // Parse the raw email message

    const parser = new PostalMime.default();

    const rawEmail = new Response(message.raw);

    const email = await parser.parse(await rawEmail.arrayBuffer());


    // Check if this is a bounce notification

    if (isBounceNotification(email)) {

      const bounceInfo = await parseBounceInfo(email);


      if (bounceInfo.type === 'hard') {

        await handleHardBounce(bounceInfo, env);

        console.log(`Hard bounce processed for: ${bounceInfo.originalRecipient}`);

        return;

      }

    }


    // Forward non-bounce emails normally

    await message.forward('admin@yourdomain.com');

  },

};


function isBounceNotification(email) {

  // Check common bounce indicators

  const subject = email.subject?.toLowerCase() || '';

  const fromAddress = email.from?.address?.toLowerCase() || '';


  // Common bounce indicators

  const bounceSubjects = [

    'mail delivery failed',

    'undelivered mail returned to sender',

    'delivery status notification',

    'returned mail',

    'mail system error'

  ];


  const bounceFromPatterns = [

    'mailer-daemon',

    'mail-daemon',

    'postmaster',

    'noreply',

    'bounce'

  ];


  return bounceSubjects.some(phrase => subject.includes(phrase)) ||

         bounceFromPatterns.some(pattern => fromAddress.includes(pattern));

}


async function parseBounceInfo(email) {

  const text = email.text || '';

  const html = email.html || '';

  const content = text + ' ' + html;


  // Extract original recipient email

  const recipientMatch = content.match(/(?:to|for|recipient):\s*([^\s<]+@[^\s>]+)/i) ||

                        content.match(/([^\s<]+@[^\s>]+)/);


  const originalRecipient = recipientMatch ? recipientMatch[1] : null;


  // Determine bounce type based on content

  const hardBounceIndicators = [

    'user unknown',

    'no such user',

    'invalid recipient',

    'recipient address rejected',

    'mailbox unavailable',

    'domain not found',

    '5.1.1', // SMTP error code for bad destination mailbox

    '5.1.2', // SMTP error code for bad destination system

    '5.4.1', // SMTP error code for no answer from host

  ];


  const isHardBounce = hardBounceIndicators.some(indicator =>

    content.toLowerCase().includes(indicator.toLowerCase())

  );


  return {

    type: isHardBounce ? 'hard' : 'soft',

    originalRecipient,

    reason: extractBounceReason(content),

    timestamp: new Date().toISOString()

  };

}


function extractBounceReason(content) {

  // Extract the specific error message

  const reasonPatterns = [

    /diagnostic[- ]code:\s*(.+)/i,

    /reason:\s*(.+)/i,

    /error:\s*(.+)/i,

    /(5\.\d+\.\d+[^.\n]*)/i

  ];


  for (const pattern of reasonPatterns) {

    const match = content.match(pattern);

    if (match) {

      return match[1].trim().split('\n')[0]; // Take first line only

    }

  }


  return 'Unknown bounce reason';

}


async function handleHardBounce(bounceInfo, env) {

  if (!bounceInfo.originalRecipient) {

    console.log('Could not extract original recipient from bounce');

    return;

  }


  // Add to suppression list in KV

  await env.SUPPRESSION_LIST.put(

    bounceInfo.originalRecipient,

    JSON.stringify({

      type: 'hard_bounce',

      reason: bounceInfo.reason,

      timestamp: bounceInfo.timestamp,

      status: 'suppressed'

    }),

    {

      metadata: {

        bounceType: 'hard',

        addedDate: bounceInfo.timestamp

      }

    }

  );


  console.log(`Added ${bounceInfo.originalRecipient} to suppression list: ${bounceInfo.reason}`);

}


```

Explain Code

## Testing hard bounce handling

Create a test bounce notification:

Terminal window

```

curl --request POST 'http://localhost:8787/cdn-cgi/handler/email' \

  --url-query 'from=mailer-daemon@example.com' \

  --url-query 'to=bounce-handler@yourdomain.com' \

  --header 'Content-Type: application/json' \

  --data-raw 'From: Mail Delivery Subsystem <mailer-daemon@example.com>

To: bounce-handler@yourdomain.com

Subject: Mail delivery failed: returning message to sender

Date: Wed, 28 Aug 2024 10:30:00 +0000

Message-ID: <bounce123@example.com>


This message was created automatically by mail delivery software.


A message that you sent could not be delivered to one or more of its

recipients. This is a permanent error. The following address(es) failed:


  nonexistent@example.com

    SMTP error from remote mail server after RCPT TO:<nonexistent@example.com>:

    host mx.example.com [192.168.1.1]: 550 5.1.1 User unknown


------ This is a copy of the message, including all the headers. ------


Return-path: <sender@yourdomain.com>

From: sender@yourdomain.com

To: nonexistent@example.com

Subject: Welcome to our service

Message-ID: <original123@yourdomain.com>


Welcome! Thanks for signing up.'


```

Explain Code

## Checking suppression list

Add a utility function to check if an email is suppressed before sending:

JavaScript

```

async function isEmailSuppressed(email, env) {

  const suppressionEntry = await env.SUPPRESSION_LIST.get(email);


  if (suppressionEntry) {

    const data = JSON.parse(suppressionEntry);

    console.log(`Email ${email} is suppressed: ${data.reason}`);

    return true;

  }


  return false;

}


// Use before sending emails

export async function sendEmail(recipient, subject, content, env) {

  if (await isEmailSuppressed(recipient, env)) {

    console.log(`Skipping email to suppressed address: ${recipient}`);

    return { success: false, reason: "suppressed" };

  }


  // Proceed with email sending

  // ... your email sending logic

}


```

Explain Code

## Best practices

1. **Monitor bounce rates**: Track bounce rates to maintain good sender reputation
2. **Automatic cleanup**: Regularly review and clean suppression lists
3. **Double opt-in**: Use double opt-in to reduce invalid addresses
4. **Retry logic**: Implement appropriate retry logic for soft bounces
5. **Logging**: Log all bounce handling for debugging and analytics

## Next steps

* Learn about [email authentication](https://developers.cloudflare.com/email-service/concepts/email-authentication/) to improve deliverability
* Set up [metrics and analytics](https://developers.cloudflare.com/email-service/observability/metrics-analytics/) to monitor bounce rates
* Implement [spam filtering](https://developers.cloudflare.com/email-service/examples/email-routing/spam-filtering/) for incoming emails

```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-routing/","name":"Email routing"}},{"@type":"ListItem","position":5,"item":{"@id":"/email-service/examples/email-routing/hard-bounce-handling/","name":"Handle hard bounce emails"}}]}
```

---

---
title: Spam filtering
description: Build spam filtering systems with keyword matching, domain validation, and intelligent detection methods for effective email security.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/examples/email-routing/spam-filtering.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Spam filtering

Implement intelligent spam detection with keyword analysis, domain reputation, and machine learning techniques

Build spam filtering systems with keyword matching, domain validation, and intelligent detection methods for effective email security.

## Basic spam filter

Simple spam detection with keyword matching and domain validation:

TypeScript

```

interface Env {

  EMAIL: SendEmail;

  EMAIL_ANALYTICS: AnalyticsEngine;

}


interface SpamFilter {

  checkSpam(

    message: any,

  ): Promise<{ isSpam: boolean; score: number; reasons: string[] }>;

}


class SimpleSpamFilter implements SpamFilter {

  private spamKeywords = [

    "buy now",

    "limited time",

    "act fast",

    "click here",

    "free money",

    "guaranteed",

    "risk free",

    "urgent",

    "winner",

    "congratulations",

    "inheritance",

    "lottery"

  ];


  private trustedDomains = ["company.com", "trusted-partner.com", "vendor.net"];


  async checkSpam(

    message,

  ): Promise<{ isSpam: boolean; score: number; reasons: string[] }> {

    let score = 0;

    const reasons = [];


    const sender = message.from;

    const subject = message.headers.get("subject") || "";

    const senderDomain = sender.split("@")[1];


    // Check sender domain

    if (this.trustedDomains.includes(senderDomain)) {

      score -= 2; // Trusted sender

    }


    // Check subject for spam keywords

    const subjectLower = subject.toLowerCase();

    for (const keyword of this.spamKeywords) {

      if (subjectLower.includes(keyword)) {

        score += 1;

        reasons.push(`Spam keyword: ${keyword}`);

      }

    }


    // Check for excessive capitalization

    const capsRatio = (subject.match(/[A-Z]/g) || []).length / subject.length;

    if (capsRatio > 0.7 && subject.length > 10) {

      score += 1;

      reasons.push("Excessive capitalization");

    }


    // Check for suspicious patterns

    if (subject.includes("!!!") || subject.includes("$$$")) {

      score += 1;

      reasons.push("Suspicious punctuation");

    }


    // Check for suspicious sender patterns

    if (sender.includes("noreply") && subject.toLowerCase().includes("urgent")) {

      score += 2;

      reasons.push("Suspicious noreply + urgent combination");

    }


    return {

      isSpam: score >= 2,

      score,

      reasons,

    };

  }

}


const spamFilter = new SimpleSpamFilter();


export default {

  async email(message, env, ctx): Promise<void> {

    const startTime = Date.now();


    // Check for spam

    const spamCheck = await spamFilter.checkSpam(message);


    // Track spam check metrics

    env.EMAIL_ANALYTICS?.writeDataPoint({

      blobs: [

        'spam_check_completed',

        message.from,

        message.to,

        spamCheck.isSpam ? 'spam' : 'legitimate'

      ],

      doubles: [

        1, // Count

        spamCheck.score,

        Date.now() - startTime

      ],

      indexes: [

        `spam_detected:${spamCheck.isSpam}`,

        `score_range:${getScoreRange(spamCheck.score)}`

      ]

    });


    if (spamCheck.isSpam) {

      console.log(`🚫 Rejected spam email from ${message.from}: ${spamCheck.reasons.join(", ")}`);

      message.setReject(`Message rejected: ${spamCheck.reasons[0]}`);

      return;

    }


    // Add spam score headers and forward

    const headers = new Headers();

    headers.set("X-Spam-Score", spamCheck.score.toString());

    headers.set("X-Spam-Reasons", spamCheck.reasons.join(", "));

    headers.set("X-Spam-Check-Time", (Date.now() - startTime).toString());


    await message.forward("inbox@company.com", headers);

  },

};


function getScoreRange(score: number): string {

  if (score < 0) return 'trusted';

  if (score === 0) return 'neutral';

  if (score === 1) return 'suspicious';

  return 'spam';

}


```

Explain Code

## Advanced spam detection with AI

For more sophisticated spam detection, you can enhance the basic filter using [Workers AI](https://developers.cloudflare.com/workers-ai) to analyze email content with machine learning models. This approach can identify subtle spam patterns that keyword-based filters might miss.

```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-routing/","name":"Email routing"}},{"@type":"ListItem","position":5,"item":{"@id":"/email-service/examples/email-routing/spam-filtering/","name":"Spam filtering"}}]}
```

---

---
title: Email sending
description: Advanced patterns and examples for sending emails with Cloudflare Email Service. These examples use the Workers binding. If you are using the REST API, the same EmailMessage fields (to, from, subject, html, attachments, headers) apply — send them as JSON in your HTTP request body.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/examples/email-sending/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Email sending

Advanced patterns and examples for sending emails with Cloudflare Email Service. These examples use the [Workers binding](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/). If you are using the [REST API](https://developers.cloudflare.com/email-service/api/send-emails/rest-api/), the same `EmailMessage` fields (`to`, `from`, `subject`, `html`, `attachments`, `headers`) apply — send them as JSON in your HTTP request body.

* [ User signup flow ](https://developers.cloudflare.com/email-service/examples/email-sending/signup-flow/)
* [ Magic link authentication ](https://developers.cloudflare.com/email-service/examples/email-sending/magic-link/)
* [ Email attachments ](https://developers.cloudflare.com/email-service/examples/email-sending/email-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"}}]}
```

---

---
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
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/examples/email-sending/email-attachments.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# 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.

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 });

  },

};


```

Explain Code

## 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);

  const pdfBase64 = btoa(pdfContent);


  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: pdfBase64,

        contentType: "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;

}


```

Explain Code

## 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 (base64 encoded)

  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.png",

        content: logoImage,

        contentType: "image/png",

        disposition: "inline",

        contentId: "company-logo",

      },

      {

        filename: "performance-chart.png",

        content: chartImage,

        contentType: "image/png",

        disposition: "inline",

        contentId: "performance-chart",

      },

    ],

  });


  return new Response(

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

  );

}


function generateChartImage(reportData: any): string {

  // 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 btoa(chartSVG);

}


function getCompanyLogo(): string {

  // 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 btoa(logoSVG);

}


```

Explain Code

## 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 },

    );

  }


  // Convert file to base64

  const fileBuffer = await file.arrayBuffer();

  const fileBase64 = btoa(String.fromCharCode(...new Uint8Array(fileBuffer)));


  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: fileBase64,

        contentType: 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 } {

  const maxSize = 25 * 1024 * 1024; // 25MB limit

  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";

}


```

Explain Code

```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"}}]}
```

---

---
title: Magic link authentication
description: Passwordless login system using magic links sent via email with JWT tokens and session management.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/examples/email-sending/magic-link.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Magic link authentication

Implement passwordless authentication by sending secure, time-limited login links via email.

This example demonstrates how to send a magic link email for passwordless authentication using Cloudflare Email Service.

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-magic-link" && request.method === "POST") {

      return handleSendMagicLink(request, env);

    }


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

  },

};


async function handleSendMagicLink(

  request: Request,

  env: Env,

): Promise<Response> {

  const { email } = await request.json();


  if (!email || !isValidEmail(email)) {

    return new Response(JSON.stringify({ error: "Invalid email" }), {

      status: 400,

    });

  }


  // Generate a simple secure token (you would implement proper JWT/token handling)

  const token = crypto.randomUUID();

  const magicUrl = `https://${env.DOMAIN}/login?token=${token}`;


  // Send magic link email

  await env.EMAIL.send({

    to: email,

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

    subject: "Your login link",

    html: `

      <h1>Login to your account</h1>

      <p>Click the link below to log in:</p>

      <p><a href="https://developers.cloudflare.com/email-service/examples/email-sending/magic-link/%3C/span%3E%3Cspan%20style="--0:#89DDFF;--1:#007474">${magicUrl}">Login Now</a></p>

      <p>This link expires in 15 minutes.</p>

    `,

    text: `

      Login to your account


      Click this link to log in: ${magicUrl}


      This link expires in 15 minutes.

    `,

  });


  return new Response(

    JSON.stringify({

      success: true,

      message: "Magic link sent to your email",

    }),

  );

}


function isValidEmail(email: string): boolean {

  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);

}


```

Explain Code

```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/magic-link/","name":"Magic link authentication"}}]}
```

---

---
title: User signup flow
description: Handle user registration with automated welcome emails and email verification using secure tokens.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/examples/email-sending/signup-flow.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# User signup flow

Send welcome and verification emails during user signup with secure token-based email verification.

This example demonstrates a complete user registration system with welcome emails and email verification.

TypeScript

```

interface Env {

  EMAIL: SendEmail;

  USERS: KVNamespace;

  VERIFICATION_TOKENS: KVNamespace;

  DOMAIN: string;

  COMPANY_NAME: string;

}


interface User {

  id: string;

  email: string;

  firstName: string;

  verified: boolean;

  createdAt: string;

}


export default {

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

    const url = new URL(request.url);


    if (url.pathname === "/signup" && request.method === "POST") {

      return handleSignup(request, env);

    }


    if (url.pathname === "/verify" && request.method === "GET") {

      return handleVerification(request, env);

    }


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

  },

};


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

  const { email, firstName } = await request.json();


  // Validate and check for existing user

  if (!email || !firstName || !isValidEmail(email)) {

    return new Response(JSON.stringify({ error: "Invalid input" }), {

      status: 400,

    });

  }


  if (await env.USERS.get(email)) {

    return new Response(JSON.stringify({ error: "User already exists" }), {

      status: 409,

    });

  }


  // Create user

  const userId = crypto.randomUUID();

  const user: User = {

    id: userId,

    email,

    firstName,

    verified: false,

    createdAt: new Date().toISOString(),

  };


  await env.USERS.put(email, JSON.stringify(user));


  // Generate verification token (expires in 1 hour)

  const verificationToken = crypto.randomUUID();

  await env.VERIFICATION_TOKENS.put(verificationToken, email, {

    expirationTtl: 3600,

  });


  // Send welcome email

  await env.EMAIL.send({

    to: email,

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

    subject: `Welcome to ${env.COMPANY_NAME}!`,

    html: `

      <h1>Welcome ${firstName}!</h1>

      <p>Thanks for joining ${env.COMPANY_NAME}. Please verify your email to get started.</p>

    `,

  });


  // Send verification email

  const verificationUrl = `https://${env.DOMAIN}/verify?token=${verificationToken}`;

  await env.EMAIL.send({

    to: email,

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

    subject: "Please verify your email address",

    html: `

      <h1>Verify Your Email</h1>

      <p>Hi ${firstName}! Click the link below to verify your email:</p>

      <a href="https://developers.cloudflare.com/email-service/examples/email-sending/signup-flow/%3C/span%3E%3Cspan%20style="--0:#89DDFF;--1:#007474">${verificationUrl}" style="background: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px;">Verify Email</a>

      <p>This link expires in 1 hour.</p>

    `,

  });


  return new Response(

    JSON.stringify({

      success: true,

      message: "Account created. Please check your email.",

      userId,

    }),

    { status: 201 },

  );

}


async function handleVerification(

  request: Request,

  env: Env,

): Promise<Response> {

  const url = new URL(request.url);

  const token = url.searchParams.get("token");


  if (!token) {

    return new Response("Missing token", { status: 400 });

  }


  // Verify token

  const userEmail = await env.VERIFICATION_TOKENS.get(token);

  if (!userEmail) {

    return new Response("Invalid or expired token", { status: 400 });

  }


  // Get and update user

  const userData = await env.USERS.get(userEmail);

  if (!userData) {

    return new Response("User not found", { status: 404 });

  }


  const user: User = JSON.parse(userData);

  user.verified = true;


  await env.USERS.put(user.email, JSON.stringify(user));

  await env.VERIFICATION_TOKENS.delete(token);


  return new Response(

    `

    <html>

      <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">

        <h1>✅ Email Verified!</h1>

        <p>Welcome ${user.firstName}! Your account is now active.</p>

        <a href="https://developers.cloudflare.com/" style="background: #28a745; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px;">Continue</a>

      </body>

    </html>

  `,

    {

      headers: { "Content-Type": "text/html" },

    },

  );

}


function isValidEmail(email: string): boolean {

  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);

}


```

Explain Code

```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/signup-flow/","name":"User signup flow"}}]}
```

---

---
title: Email routing
description: Test email routing behavior locally using wrangler dev to simulate incoming emails and verify your routing logic before deploying.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/local-development/routing.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Email routing

Test email routing Workers locally using wrangler dev with simulated incoming emails

Test email routing behavior locally using `wrangler dev` to simulate incoming emails and verify your routing logic before deploying.

## Prerequisites

1. Sign up for a [Cloudflare account ↗](https://dash.cloudflare.com/sign-up/workers-and-pages).
2. Install [Node.js ↗](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).

Node.js version manager

Use a Node version manager like [Volta ↗](https://volta.sh/) or [nvm ↗](https://github.com/nvm-sh/nvm) to avoid permission issues and change Node.js versions. [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/), discussed later in this guide, requires a Node version of `16.17.0` or later.

## Configuration

Configure your Wrangler file with the email binding:

* [  wrangler.jsonc ](#tab-panel-6831)
* [  wrangler.toml ](#tab-panel-6832)

JSONC

```

{

  "$schema": "./node_modules/wrangler/config-schema.json",

  "name": "email-routing-worker",

  // Set this to today's date

  "compatibility_date": "2026-04-17",

  "send_email": [

    {

      "name": "EMAIL"

    }

  ]

}


```

Explain Code

TOML

```

name = "email-routing-worker"

# Set this to today's date

compatibility_date = "2026-04-17"


[[send_email]]

name = "EMAIL"


```

## Basic routing worker

JavaScript

```

import * as PostalMime from 'postal-mime';


export default {

  async email(message, env, ctx) {

    // Parse the raw email message

    const parser = new PostalMime.default();

    const rawEmail = new Response(message.raw);

    const email = await parser.parse(await rawEmail.arrayBuffer());


    console.log('Received email:', {

      from: message.from,

      to: message.to,

      subject: email.subject,

      text: email.text,

      html: email.html

    });


    // Route based on recipient

    if (message.to.includes('support@')) {

      await message.forward('support-team@company.com');

    } else {

      await message.forward('general@company.com');

    }

  },

};


```

Explain Code

## Testing

Start your development server:

Terminal window

```

npx wrangler dev


```

Send a test email using the local endpoint. The request body must be a raw email message in [RFC 5322 ↗](https://datatracker.ietf.org/doc/html/rfc5322) format, and the message must include a `Message-ID` header:

Terminal window

```

curl --request POST 'http://localhost:8787/cdn-cgi/handler/email' \

  --url-query 'from=sender@example.com' \

  --url-query 'to=recipient@example.com' \

  --data-raw 'Received: from smtp.example.com (127.0.0.1)

        by cloudflare-email.com (unknown) id 4fwwffRXOpyR

        for <recipient@example.com>; Tue, 27 Aug 2024 15:50:20 +0000

From: "John" <sender@example.com>

Reply-To: sender@example.com

To: recipient@example.com

Subject: Testing Email Workers Local Dev

Content-Type: text/html; charset="windows-1252"

X-Mailer: Curl

Date: Tue, 27 Aug 2024 08:49:44 -0700

Message-ID: <6114391943504294873000@ZSH-GHOSTTY>


Hi there'


```

Explain Code

This will output the parsed email structure in the console:

```

{

  "headers": [

    {

      "key": "received",

      "value": "from smtp.example.com (127.0.0.1) by cloudflare-email.com (unknown) id 4fwwffRXOpyR for <recipient@example.com>; Tue, 27 Aug 2024 15:50:20 +0000"

    },

    { "key": "from", "value": "\"John\" <sender@example.com>" },

    { "key": "reply-to", "value": "sender@example.com" },

    { "key": "to", "value": "recipient@example.com" },

    { "key": "subject", "value": "Testing Email Workers Local Dev" },

    { "key": "content-type", "value": "text/html; charset=\"windows-1252\"" },

    { "key": "x-mailer", "value": "Curl" },

    { "key": "date", "value": "Tue, 27 Aug 2024 08:49:44 -0700" },

    {

      "key": "message-id",

      "value": "<6114391943504294873000@ZSH-GHOSTTY>"

    }

  ],

  "from": { "address": "sender@example.com", "name": "John" },

  "to": [{ "address": "recipient@example.com", "name": "" }],

  "replyTo": [{ "address": "sender@example.com", "name": "" }],

  "subject": "Testing Email Workers Local Dev",

  "messageId": "<6114391943504294873000@ZSH-GHOSTTY>",

  "date": "2024-08-27T15:49:44.000Z",

  "html": "Hi there\n",

  "attachments": []

}


```

Explain Code

## Next steps

* Deploy your routing worker: [Route emails get started](https://developers.cloudflare.com/email-service/get-started/route-emails/)
* See advanced patterns: [Email routing examples](https://developers.cloudflare.com/email-service/examples/email-routing/)

```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/local-development/","name":"Local development"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/local-development/routing/","name":"Email routing"}}]}
```

---

---
title: Email sending
description: Test email sending functionality locally using wrangler dev to simulate email delivery and verify your sending logic before deploying.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/local-development/sending.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Email sending

Test email sending Workers locally using wrangler dev with simulated email delivery

Test email sending functionality locally using `wrangler dev` to simulate email delivery and verify your sending logic before deploying.

Note

If you are using the [REST API](https://developers.cloudflare.com/email-service/api/send-emails/rest-api/) instead of Workers, you can test by sending requests directly with `curl` or any HTTP client without a local development server. The rest of this page covers the Workers local development flow.

## Prerequisites

1. Sign up for a [Cloudflare account ↗](https://dash.cloudflare.com/sign-up/workers-and-pages).
2. Install [Node.js ↗](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).

Node.js version manager

Use a Node version manager like [Volta ↗](https://volta.sh/) or [nvm ↗](https://github.com/nvm-sh/nvm) to avoid permission issues and change Node.js versions. [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/), discussed later in this guide, requires a Node version of `16.17.0` or later.

## Configuration

Configure your Wrangler file with the email binding:

* [  wrangler.jsonc ](#tab-panel-6833)
* [  wrangler.toml ](#tab-panel-6834)

JSONC

```

{

  "$schema": "./node_modules/wrangler/config-schema.json",

  "name": "email-sending-worker",

  "compatibility_date": "2024-01-01",

  "send_email": [

    {

      "name": "EMAIL"

    }

  ]

}


```

Explain Code

TOML

```

name = "email-sending-worker"

compatibility_date = "2024-01-01"


[[send_email]]

name = "EMAIL"


```

## Remote bindings (recommended)

Using [remote bindings](https://developers.cloudflare.com/workers/development-testing/#remote-bindings) is the recommended way to develop with Email Service locally. By default, `wrangler dev` simulates the email binding locally -- emails are logged to the console but not actually sent. With remote bindings, your Worker runs locally but sends real emails through Email Service.

Set `remote: true` on the email binding in your Wrangler configuration:

* [  wrangler.jsonc ](#tab-panel-6835)
* [  wrangler.toml ](#tab-panel-6836)

JSONC

```

{

  "$schema": "./node_modules/wrangler/config-schema.json",

  "name": "email-sending-worker",

  // Set this to today's date

  "compatibility_date": "2026-04-17",

  "send_email": [

    {

      "name": "EMAIL",

      "remote": true

    }

  ]

}


```

Explain Code

TOML

```

name = "email-sending-worker"

# Set this to today's date

compatibility_date = "2026-04-17"


[[send_email]]

name = "EMAIL"

remote = true


```

Then run `wrangler dev` as usual. Calls to `env.EMAIL.send()` will send actual emails through Email Service while your Worker code runs locally.

Warning

Remote bindings send real emails to real recipients. Use test email addresses to avoid sending unintended emails during development.

## Local simulation

When running `wrangler dev` without remote bindings, the email binding is simulated locally. Emails are not sent -- instead, the email content is logged to the console and saved to local files for inspection.

## Basic sending worker

JavaScript

```

export default {

  async fetch(request, env, ctx) {

    if (request.method !== "POST") {

      return new Response("Method not allowed", { status: 405 });

    }


    try {

      const emailData = await request.json();


      console.log("📤 Sending email:", {

        to: emailData.to,

        from: emailData.from,

        subject: emailData.subject,

      });


      const response = await env.EMAIL.send(emailData);


      return new Response(

        JSON.stringify({

          success: true,

          id: response.messageId,

        }),

        {

          headers: { "Content-Type": "application/json" },

        },

      );

    } catch (error) {

      return new Response(

        JSON.stringify({

          success: false,

          error: error.message,

        }),

        {

          status: 500,

          headers: { "Content-Type": "application/json" },

        },

      );

    }

  },

};


```

Explain Code

## Testing locally

Start your development server:

Terminal window

```

npx wrangler dev


```

Send a test email:

Terminal window

```

curl -X POST http://localhost:8787/ \

  -H "Content-Type: application/json" \

  -d '{

    "to": "recipient@example.com",

    "from": "sender@yourdomain.com",

    "subject": "Test Email",

    "html": "<h1>Hello from Wrangler!</h1>",

    "text": "Hello from Wrangler!"

  }'


```

Wrangler will show output like:

```

[wrangler:info] send_email binding called with MessageBuilder:

From: sender@yourdomain.com

To: recipient@example.com

Subject: Test Email


Text: /tmp/miniflare-.../files/email-text/<message-id>.txt


```

The email content (text and HTML) is saved to local files that you can inspect to verify your email structure before deploying.

## Known limitations

### Binary attachments

Local development simulates the `send_email` binding locally, but `ArrayBuffer` values in attachment `content` cannot be serialized by the local simulator. If you pass an `ArrayBuffer` (for example, for image or PDF attachments), you will see an error like:

```

Cannot serialize value: [object ArrayBuffer]


```

**Workaround:** Use string content for text-based attachments during local development. To test binary attachments (images, PDFs), deploy your Worker with `npx wrangler deploy` and test against the deployed version.

This limitation only affects local development — `ArrayBuffer` content works correctly on deployed Workers.

## Next steps

* Deploy your sending worker: [Send emails get started](https://developers.cloudflare.com/email-service/get-started/send-emails/)
* See advanced patterns: [Email sending examples](https://developers.cloudflare.com/email-service/examples/email-sending/)

```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/local-development/","name":"Local development"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/local-development/sending/","name":"Email sending"}}]}
```

---

---
title: Email Logs
description: Email Service provides comprehensive logging for both email sending and routing activities. Access detailed logs through the Cloudflare dashboard to monitor email flow, troubleshoot delivery issues, and analyze authentication status.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/observability/logs.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Email Logs

View and analyze email sending and routing activity logs with detailed authentication and delivery information

Email Service provides comprehensive logging for both email sending and routing activities. Access detailed logs through the Cloudflare dashboard to monitor email flow, troubleshoot delivery issues, and analyze authentication status.

## Activity Log

The Activity Log allows you to sort through all email activities and check actions taken by Email Service.

### Email sending logs

For outbound emails sent through Email Service:

* **Sent**: Email successfully accepted and queued for delivery
* **Delivered**: Email successfully delivered to recipient's mail server
* **Delivery failed**: Email bounced (hard or soft bounce). This corresponds to the `deliveryFailed` status in the [GraphQL Analytics API](https://developers.cloudflare.com/email-service/observability/metrics-analytics/).
* **Rejected**: Email was not sent because the recipient is on your account's [suppression list](https://developers.cloudflare.com/email-service/concepts/suppressions/).
* **Failed**: Email failed to send due to configuration or authentication issues

### Email routing logs

For inbound emails processed through Email Routing:

* **Forwarded**: Email successfully forwarded to destination address
* **Dropped**: Email dropped due to filtering rules or configuration
* **Rejected**: Email rejected due to SPF, DKIM, or DMARC failures
* **Processed**: Email processed by Worker handler

## Viewing email details

Select any email in the Activity Log to expand its details and view comprehensive information:

### Authentication status

Check the status of email authentication protocols:

* **SPF status**: Shows pass/fail for Sender Policy Framework validation
* **DKIM status**: Shows pass/fail for DomainKeys Identified Mail signature verification
* **DMARC status**: Shows pass/fail for Domain-based Message Authentication compliance

### Email headers

View complete email headers including:

* Message-ID and threading information
* Authentication-Results headers
* Custom headers added during processing
* Routing and delivery timestamps

### Delivery information

For sent emails, see delivery details:

* Recipient mail server response
* Delivery attempts and timestamps
* Bounce reason codes and categories
* Final delivery status

## Best practices for log monitoring

### Regular review

* Monitor logs daily during initial setup
* Check weekly for ongoing operations
* Review immediately after configuration changes

### Key metrics to watch

* Authentication failure rates
* Bounce patterns and trends
* Delivery success rates

### Troubleshooting workflow

1. Identify the issue: Use logs to pinpoint failure types
2. Check authentication: Verify SPF, DKIM, DMARC configuration
3. Adjust configuration: Make necessary DNS or routing changes
4. Monitor improvement: Track metrics after changes

---

Email logs provide the visibility needed to maintain high deliverability and properly route incoming emails. Use this data to optimize your email configuration and quickly resolve any delivery issues.

```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/observability/","name":"Observability & Logs"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/observability/logs/","name":"Email Logs"}}]}
```

---

---
title: Metrics and analytics
description: Email Service exposes analytics that allow you to inspect email sending performance and delivery rates across all your domains.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/observability/metrics-analytics.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Metrics and analytics

Email Service exposes analytics that allow you to inspect email sending performance and delivery rates across all your domains.

The metrics displayed in the [Cloudflare dashboard ↗](https://dash.cloudflare.com/) charts are queried from Cloudflare's [GraphQL Analytics API](https://developers.cloudflare.com/analytics/graphql-api/). You can access the metrics [programmatically](#query-via-the-graphql-api) via GraphQL or HTTP client.

## Metrics

Email Service currently exposes the below metrics:

| Dataset              | GraphQL Dataset Name       | Description                                                                                                               |
| -------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Sending (aggregated) | emailSendingAdaptiveGroups | Aggregated email sending counts grouped by dimensions such as status, date, sending domain, and authentication results.   |
| Sending (events)     | emailSendingAdaptive       | Individual email sending events with full detail including sender, recipient, subject, message ID, and error information. |

Metrics can be queried (and are retained) for the past 31 days.

## View metrics in the dashboard

Per-domain analytics for Email Service are available in the Cloudflare dashboard. To view current and historical metrics:

1. Log in to the [Cloudflare dashboard ↗](https://dash.cloudflare.com) and select your account.
2. Go to [**Compute** \> **Email Service** ↗](https://dash.cloudflare.com/?to=/:account/email-service/sending).
3. Select an existing domain or view account-wide metrics.
4. Select the **Analytics** tab.

You can optionally select a time window to query. This defaults to the last 24 hours.

## Query via the GraphQL API

You can programmatically query analytics for your Email Service domains via the [GraphQL Analytics API](https://developers.cloudflare.com/analytics/graphql-api/). This API queries the same datasets as the Cloudflare dashboard, and supports GraphQL [introspection](https://developers.cloudflare.com/analytics/graphql-api/features/discovery/introspection/).

To get started using the [GraphQL Analytics API](https://developers.cloudflare.com/analytics/graphql-api/), follow the documentation to setup [Authentication for the GraphQL Analytics API](https://developers.cloudflare.com/analytics/graphql-api/getting-started/authentication/). Your API token must include the **Analytics Read** permission.

These are **zone-level** datasets. To query them, provide your zone ID (not account ID) as the `zoneTag` filter. The GraphQL datasets for Email Service include:

* `emailSendingAdaptiveGroups` — aggregated counts with groupable dimensions
* `emailSendingAdaptive` — individual email events

### Available dimensions

The `emailSendingAdaptiveGroups` dataset supports the following dimensions for grouping and filtering:

| Dimension              | Type   | Description                                              |
| ---------------------- | ------ | -------------------------------------------------------- |
| date                   | Date   | Day-level grouping                                       |
| datetime               | Time   | Exact event timestamp                                    |
| datetimeMinute         | Time   | Minute-level grouping                                    |
| datetimeFiveMinutes    | Time   | 5-minute interval grouping                               |
| datetimeFifteenMinutes | Time   | 15-minute interval grouping                              |
| datetimeHour           | Time   | Hour-level grouping                                      |
| status                 | string | Delivery status (for example, delivered, deliveryFailed) |
| eventType              | string | Type of sending event                                    |
| sendingDomain          | string | The domain used to send the email                        |
| envelopeTo             | string | Recipient envelope address                               |
| errorCause             | string | Error cause for failed sends                             |
| arc                    | string | ARC authentication result                                |
| dkim                   | string | DKIM authentication result                               |
| dmarc                  | string | DMARC authentication result                              |
| spf                    | string | SPF authentication result                                |
| isSpam                 | uint8  | Whether the email was flagged as spam                    |
| isNDR                  | uint8  | Whether the email is a non-delivery report               |

The `emailSendingAdaptive` dataset includes all of the above plus per-event fields: `from`, `to`, `subject`, `messageId`, `sessionId`, `errorDetail`.

### Examples

The following are common GraphQL queries that you can use to retrieve information about Email Service analytics. These queries use the variable `$zoneTag`, which should be set to your Cloudflare Zone ID. You can find this in the Cloudflare dashboard under your domain's **Overview** page.

```

{

  "zoneTag": "<YOUR_ZONE_ID>",

  "start": "2024-07-15",

  "end": "2024-07-30"

}


```

#### Email sending operations

To query the count of emails for a given date range, grouped by `date` and `status` (for example, `delivered`, `deliveryFailed`):

```

query EmailSendingByStatus($zoneTag: string!, $start: Date!, $end: Date!) {

  viewer {

    zones(filter: { zoneTag: $zoneTag }) {

      emailSendingAdaptiveGroups(

        filter: { date_geq: $start, date_leq: $end }

        limit: 10000

        orderBy: [date_DESC]

      ) {

        count

        dimensions {

          date

          status

        }

      }

    }

  }

}


```

[Run in GraphQL API Explorer](https://graphql.cloudflare.com/explorer?query=I4VwpgTgngBAogWwIYEsA2BlMA7AJi7AcwCEoMAXJckAZwAoASALwHtswAVJQgLhhvIQChAIQAaGAwFII5PgBEqYcZJy4FSkQEoYAbwBQMGADcUYAO6Q9hozFbt6AM3TlIfXXbadufZl66EMAC+Oga2tmDI6Fh4wgCCuEgADuQoxmAA4hAsIEn0NuFGzmiuEO4wia4A+oRgwL7SshKVYFVodb5qwQWFaCgIKHIwAIwADOOjPeEsELiQpHwA2i1V8nAYAMIAulMwobtGAMY52OQHFf04NChsNNaFhS3nRtLUNOdBu5-h391BQA&variables=N4IgXg9gdgpgKgQwOYgFwgFoHkByBRAfQEkAREAGhAGcAXBAJxrRACYAGFgNgFo2AWbgEZOFEDCgATZuy68BggOwgAvkA)

#### Delivery failure analysis

To investigate delivery failure causes for a specific date range, grouped by `errorCause` and `sendingDomain`:

```

query EmailDeliveryFailures($zoneTag: string!, $start: Date!, $end: Date!) {

  viewer {

    zones(filter: { zoneTag: $zoneTag }) {

      emailSendingAdaptiveGroups(

        filter: { date_geq: $start, date_leq: $end, status: "deliveryFailed" }

        limit: 10000

        orderBy: [date_DESC]

      ) {

        count

        dimensions {

          date

          errorCause

          sendingDomain

        }

      }

    }

  }

}


```

[Run in GraphQL API Explorer](https://graphql.cloudflare.com/explorer?query=I4VwpgTgngBAogWwIYEsA2ARMaUDdJQBiqaIEYAzgBQAkAXgPYB2YAKkgOYBcMFALhBRMOAQgA0MGvyQQ+PDEj5hxksEwAm8xcoCUMAN4AoGDFwowAd0gHjJmIxbUAZuiUQe++8zace9b+wcMAC+ekZ2dmDI6ADKaupCHACC6kgADnx4YADiEAwgadS2ESYuaG4eMKlKAPocYMB+0rIS1WA1aA1+8RLSfCAUPABE6thZ0MToYOpDIcUlOAgocjAAjAAMm+vzEQwQoxAAQlA8ANptNRhwMQDCALo7MGGPJgDG+Ux8L1UoCGoUKGYFBsJRKbW+JkgeQgNyQAzAEN48USGAY0SY32CjyxERxc2CQA&variables=N4IgXg9gdgpgKgQwOYgFwgFoHkByBRAfQEkAREAGhAGcAXBAJxrRACYAGFgNgFo2AWbgEZOFEDCgATZuy68BggOwgAvkA)

#### Hourly volume

To query email sending volume grouped by hour, useful for identifying traffic patterns:

```

query EmailSendingHourlyVolume($zoneTag: string!, $start: Time!, $end: Time!) {

  viewer {

    zones(filter: { zoneTag: $zoneTag }) {

      emailSendingAdaptiveGroups(

        filter: { datetimeHour_geq: $start, datetimeHour_leq: $end }

        limit: 10000

        orderBy: [datetimeHour_ASC]

      ) {

        count

        dimensions {

          datetimeHour

          status

        }

      }

    }

  }

}


```

[Run in GraphQL API Explorer](https://graphql.cloudflare.com/explorer?query=I4VwpgTgngBAogWwIYEsA2BlMA7AJi7AcwAkB7ECNKANVLRATAAoASAL1OzABUlCAuGAGcALhAKEAhABoYLUUggjB3FIxlycuFWrCSAlDADeAKBgwAbijAB3SMbPmYHLkKYAzdCMiCjzzjx8guwBvIQwAL6Gpk5OYMjoWHgSAIK4SAAOIigWYADiEOQZbo6x5p5o3hC+MOne2YxkFAD6hGDAwQpKsnVgDWBNEM1o7cFakaVlaGooyjAAjAAMy4uTsaQQuJAAQlCCANq9-YPNKRgAwgC6azDRN+YAxuTYIve1uthCKJxCDmVlR10gze5gUIhAQjeERu0NisImESAA&variables=N4IgXg9gdgpgKgQwOYgFwgFoHkByBRAfQEkAREAGhAGcAXBAJxrRACYAGFgNgFo2AWbgEZOcQQE5UfTqgDMMjBRAwoAE2bsuvAYIDscNoMnS5CgL5A)

#### Individual email events

To query individual email events for troubleshooting specific delivery issues. This uses the `emailSendingAdaptive` dataset and filters by `datetime` (Time type):

```

query RecentEmailEvents($zoneTag: string!, $start: Time!, $end: Time!) {

  viewer {

    zones(filter: { zoneTag: $zoneTag }) {

      emailSendingAdaptive(

        filter: { datetime_geq: $start, datetime_leq: $end }

        limit: 50

        orderBy: [datetime_DESC]

      ) {

        datetime

        from

        to

        subject

        status

        eventType

        sendingDomain

        messageId

        errorCause

        errorDetail

        dkim

        dmarc

        spf

        isSpam

      }

    }

  }

}


```

[Run in GraphQL API Explorer](https://graphql.cloudflare.com/explorer?query=I4VwpgTgngBASmAxmAdgFwKIFsCGBLAGwwDdU0BnACgBIAvAexTABUcBzALhnLQjxTYBCADQxqPHBDRdmeLGBFjUAExlyFAShgBvAFAwYxPGADukHfoMwGTKgDNCaSF23XGLdlzrvWbGAF8tPSsrMFxCAGUVfjYAQWUcAAc0PFJKSxCDBwInCBcYBKcU+QB9NjBgLwkpUUKwYrASggqvFQCMzII5PGkYAFYABg6Q+ghlSAAhKC4AbTqGkoARDAiAYQBdYZggrYN59V2YOwh6LEO0ekPyEAAjACskNCu0HDQQckOwUnRmKESwK7RASLU74FCHeTkcjsMAASWUnwgJwgqxw7wBmVCSNGi3q+AIh2UAGs5ITcBBEFdEnZDnhyBFEjgzpj-B1WQZWf4gA&variables=N4IgXg9gdgpgKgQwOYgFwgFoHkByBRAfQEkAREAGhAGcAXBAJxrRACYAGFgNgFo2AWbgEZOcQQE5UfTqgDMMjBRAwoAE2bsuvAYIDscNoMnS5CgL5A)

Note

The `emailSendingAdaptiveGroups` dataset uses `Date` type filters (`date_geq`, `date_leq`) for day-level filtering, or `Time` type filters (`datetimeHour_geq`, etc.) for finer granularity. The `emailSendingAdaptive` dataset uses `Time` type filters (`datetime_geq`, `datetime_leq`), for example `"2024-07-15T00:00:00Z"`.

```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/observability/","name":"Observability & Logs"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/observability/metrics-analytics/","name":"Metrics and analytics"}}]}
```

---

---
title: Limits
description: Cloudflare Email Service has the following limits to ensure optimal performance and prevent abuse. These limits apply to emails sent via both the REST API and the Workers binding unless noted otherwise.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/platform/limits.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Limits

Email sending quotas, rate limits, and how to request higher limits for production use

Cloudflare Email Service has the following limits to ensure optimal performance and prevent abuse. These limits apply to emails sent via both the [REST API](https://developers.cloudflare.com/email-service/api/send-emails/rest-api/) and the [Workers binding](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/) unless noted otherwise.

## Daily sending limits

Your account may have daily sending limits based on Cloudflare's assessment of your account standing. These limits are applied on a per-account basis, may vary, and may be adjusted over time based on your sending behavior. If you need higher sending limits, contact [Cloudflare Support ↗](https://dash.cloudflare.com/support) to request an increase.

## Verified emails

When you first start using Email Service, you can send emails to [verified email addresses](https://developers.cloudflare.com/email-service/configuration/email-routing-addresses/#destination-addresses) in your account. This allows you to test and develop your application before sending to arbitrary recipients.

Accounts on a paid plan can send emails to any recipient, subject to daily sending limits.

## Email content limits

| Component                    | Limit          | Notes                                |
| ---------------------------- | -------------- | ------------------------------------ |
| **Recipients (to, cc, bcc)** | 50 per email   | Combined across all recipient fields |
| **Subject line**             | 998 characters | RFC 5322 compliant                   |
| **Total message size**       | 25 MiB         | Including attachments                |
| **Header size**              | 16 KB          | All custom headers combined          |

## Workers binding limits

The following limits apply only when sending emails via the [Workers binding](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/). They do not apply to the REST API.

| Limit           | Value            | Notes                          |
| --------------- | ---------------- | ------------------------------ |
| **CPU time**    | 50ms per request | Standard Workers CPU limit     |
| **Subrequests** | 50 per request   | Includes email send operations |
| **Memory**      | 128MB            | Standard Workers memory limit  |

## Compliance

All email sending must follow applicable anti-spam laws and regulations to maintain good standing and deliverability.

* **CAN-SPAM Act** (United States)
* **GDPR** (European Union)
* **CASL** (Canada)
* Include proper unsubscribe mechanisms
* Honor opt-out requests promptly

Need a higher limit?

To request an adjustment to a limit, complete the [Limit Increase Request Form ↗](https://forms.gle/ukpeZVLWLnKeixDu7). If the limit can be increased, Cloudflare will contact you with next steps.

```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/platform/","name":"Platform"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/platform/limits/","name":"Limits"}}]}
```

---

---
title: Pricing
description: Cloudflare Email Service pricing is based on your Cloudflare plan and email usage.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/platform/pricing.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Pricing

Cloudflare Email Service pricing is based on your Cloudflare plan and email usage.

## Plan pricing

Email Routing is available on both the Workers Free and Workers Paid plans. Email Sending is only available on the Workers Paid plan.

| Workers Free                        | Workers Paid  |                                                       |
| ----------------------------------- | ------------- | ----------------------------------------------------- |
| **Outbound emails (Email Sending)** | Not available | 3,000 included per month, then $0.35 per 1,000 emails |
| **Inbound emails (Email Routing)**  | Unlimited     | Unlimited                                             |

Email Routing Workers is billed according to [Workers pricing](https://developers.cloudflare.com/workers/platform/pricing/).

```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/platform/","name":"Platform"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/platform/pricing/","name":"Pricing"}}]}
```

---

---
title: FAQ
description: Common questions about Cloudflare Email Service.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/reference/faq.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# FAQ

Common questions about Cloudflare Email Service.

## Limits and Usage

Sending limits exist to prevent abuse and spam and to ensure high deliverability for all users. If you need higher limits, you can request a limit increase by contacting support or reaching out in the [Cloudflare Developers Discord ↗](https://discord.cloudflare.com). If you exceed your limits, emails may be queued or rejected, and you will receive error responses with rate limit information.

### What is sender reputation?

Sender reputation refers to how much inbox providers trust you to send good email to their users and not spam or scam. It is influenced by factors such as your email authentication setup, bounce and complaint rates, sending volume patterns, recipient engagement, and domain and IP history.

### Can I use this for marketing emails?

Email Service is intended only for transactional emails. We plan to support marketing emails and bulk sender tooling in the future.

### Where can I report abuse or spam?

Report abuse to: [abuse@cloudflare.com](mailto:abuse@cloudflare.com)

Include:

* Full email headers
* Description of the issue
* Any relevant account information

```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/reference/","name":"Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/reference/faq/","name":"FAQ"}}]}
```

---

---
title: Email headers
description: When sending emails with Cloudflare Email Service, you can set custom headers using the headers field in the Workers API or REST API. The Email Service uses a whitelist-based approach — only explicitly approved headers are accepted. Any header not on the whitelist (and not an X- prefixed custom header) is rejected at API time with a clear error.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/reference/headers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Email headers

Which email headers you can set, which are auto-generated, and how they are validated

When sending emails with Cloudflare Email Service, you can set custom headers using the `headers` field in the [Workers API](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/) or [REST API](https://developers.cloudflare.com/email-service/api/send-emails/rest-api/). The Email Service uses a **whitelist-based approach** — only explicitly approved headers are accepted. Any header not on the whitelist (and not an `X-` prefixed custom header) is rejected at API time with a clear error.

## Platform-controlled headers

These headers are auto-generated by the Cloudflare Email Service infrastructure. You cannot set or override them. If you include any of these in the `headers` object, the API returns `E_HEADER_NOT_ALLOWED`.

| Header                    | Behavior                                                  |
| ------------------------- | --------------------------------------------------------- |
| Date                      | UTC timestamp set at acceptance time                      |
| Message-ID                | Generated with Cloudflare domain for unique tracking      |
| MIME-Version              | Always 1.0                                                |
| Content-Type              | Generated from body parts provided                        |
| Content-Transfer-Encoding | Generated from content analysis                           |
| DKIM-Signature            | Signed by Cloudflare infrastructure                       |
| Return-Path               | Set to Cloudflare bounce processor                        |
| Received                  | Added per RFC 5321 at each hop                            |
| Feedback-ID               | Generated for Google Postmaster Tools reputation feedback |
| ARC-\*                    | Authentication chain for forwarding                       |
| TLS-Required              | Platform-controlled delivery infrastructure setting       |
| TLS-Report-Domain         | TLS failure reports route to Cloudflare infrastructure    |
| TLS-Report-Submitter      | References Cloudflare sending domain                      |
| CFBL-Address              | Complaint feedback loop address (RFC 9477)                |
| CFBL-Feedback-ID          | Complaint feedback loop ID (RFC 9477)                     |

Headers that correspond to first-class API fields — `From`, `To`, `Cc`, `Bcc`, `Subject`, `Reply-To` — are also rejected in the `headers` object with `E_HEADER_USE_API_FIELD`. Set these using the dedicated API fields (`from`, `to`, `cc`, `bcc`, `subject`, `replyTo` for Workers / `reply_to` for REST) instead.

## Whitelisted custom headers

These headers can be set via the `headers` field. Any header not listed here and not starting with `X-` is rejected with `E_HEADER_NOT_ALLOWED`.

### Threading and reply headers

| Header      | RFC                                                         | Notes                                       |
| ----------- | ----------------------------------------------------------- | ------------------------------------------- |
| In-Reply-To | [RFC 5322 ↗](https://datatracker.ietf.org/doc/html/rfc5322) | Critical for email threading in all clients |
| References  | [RFC 5322 ↗](https://datatracker.ietf.org/doc/html/rfc5322) | Critical for email threading in all clients |

### List management headers

| Header                | RFC                                                         | Notes                                                                                                                                                                    |
| --------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| List-Unsubscribe      | [RFC 2369 ↗](https://datatracker.ietf.org/doc/html/rfc2369) | Must contain <https://...> and/or <mailto:...> URI(s). HTTP (non-TLS) URIs are rejected. Gmail and Yahoo require this for bulk senders. Always DKIM-signed per RFC 8058. |
| List-Unsubscribe-Post | [RFC 8058 ↗](https://datatracker.ietf.org/doc/html/rfc8058) | Must be exactly List-Unsubscribe=One-Click (case-sensitive). Requires List-Unsubscribe with an HTTPS URI.                                                                |
| List-Id               | [RFC 2919 ↗](https://datatracker.ietf.org/doc/html/rfc2919) | List identification                                                                                                                                                      |
| List-Archive          | [RFC 2369 ↗](https://datatracker.ietf.org/doc/html/rfc2369) | URL to list archive                                                                                                                                                      |
| List-Help             | [RFC 2369 ↗](https://datatracker.ietf.org/doc/html/rfc2369) | URL for help                                                                                                                                                             |
| List-Owner            | [RFC 2369 ↗](https://datatracker.ietf.org/doc/html/rfc2369) | List owner contact                                                                                                                                                       |
| List-Post             | [RFC 2369 ↗](https://datatracker.ietf.org/doc/html/rfc2369) | Address for posting                                                                                                                                                      |
| List-Subscribe        | [RFC 2369 ↗](https://datatracker.ietf.org/doc/html/rfc2369) | Subscribe URL or address                                                                                                                                                 |
| Precedence            | De facto standard                                           | Accepted values: bulk, list, junk                                                                                                                                        |

### Automated message identification

| Header         | RFC                                                         | Notes                                               |
| -------------- | ----------------------------------------------------------- | --------------------------------------------------- |
| Auto-Submitted | [RFC 3834 ↗](https://datatracker.ietf.org/doc/html/rfc3834) | Values: auto-generated, auto-replied, auto-notified |

### Content and display

| Header           | RFC                                                         | Notes                                                     |
| ---------------- | ----------------------------------------------------------- | --------------------------------------------------------- |
| Content-Language | [RFC 3282 ↗](https://datatracker.ietf.org/doc/html/rfc3282) | Language of content (for example, en, fr)                 |
| Keywords         | [RFC 5322 ↗](https://datatracker.ietf.org/doc/html/rfc5322) | Message keywords (comma-separated for multiple values)    |
| Comments         | [RFC 5322 ↗](https://datatracker.ietf.org/doc/html/rfc5322) | Additional comments (comma-separated for multiple values) |
| Importance       | [RFC 2156 ↗](https://datatracker.ietf.org/doc/html/rfc2156) | Values: high, normal, low                                 |
| Sensitivity      | [RFC 2156 ↗](https://datatracker.ietf.org/doc/html/rfc2156) | Values: personal, private, company-confidential           |
| Organization     | [RFC 4021 ↗](https://datatracker.ietf.org/doc/html/rfc4021) | Sender's organization name                                |

### Delivery and notification

| Header                        | RFC                                                         | Notes                    |
| ----------------------------- | ----------------------------------------------------------- | ------------------------ |
| Require-Recipient-Valid-Since | [RFC 7293 ↗](https://datatracker.ietf.org/doc/html/rfc7293) | Address reuse protection |

### Modern standards

| Header      | RFC                                                         | Notes                         |
| ----------- | ----------------------------------------------------------- | ----------------------------- |
| Archived-At | [RFC 5064 ↗](https://datatracker.ietf.org/doc/html/rfc5064) | URL where message is archived |

### Custom X-headers

Any header starting with `X-` is allowed. This covers common headers like `X-Mailer`, `X-Priority`, `X-Campaign-ID`, and any custom tracking headers your application needs.

* **Name format:** `X-[A-Za-z0-9\-_]+`, maximum 100 characters
* **Value:** UTF-8, maximum 2,048 bytes
* **No count limit** on X-headers (subject to the 16 KB total payload limit)

## Usage examples

* [ REST API (curl) ](#tab-panel-6837)
* [ Workers binding ](#tab-panel-6838)

Terminal window

```

curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/email/sending/send" \

  --header "Authorization: Bearer <API_TOKEN>" \

  --header "Content-Type: application/json" \

  --data '{

    "to": "user@example.com",

    "from": "notifications@yourdomain.com",

    "subject": "Your weekly digest",

    "html": "<h1>Weekly Digest</h1>",

    "headers": {

      "In-Reply-To": "<original-message-id@yourdomain.com>",

      "References": "<original-message-id@yourdomain.com>",

      "List-Unsubscribe": "<https://yourdomain.com/unsubscribe?id=abc123>",

      "List-Unsubscribe-Post": "List-Unsubscribe=One-Click",

      "X-Campaign-ID": "weekly-digest-2026-03",

      "X-User-Segment": "premium"

    }

  }'


```

Explain Code

TypeScript

```

const response = await env.EMAIL.send({

  to: "user@example.com",

  from: "notifications@yourdomain.com",

  subject: "Your weekly digest",

  html: "<h1>Weekly Digest</h1>",

  headers: {

    // Threading

    "In-Reply-To": "<original-message-id@yourdomain.com>",

    References: "<original-message-id@yourdomain.com>",


    // List management (required by Gmail/Yahoo for bulk senders)

    "List-Unsubscribe": "<https://yourdomain.com/unsubscribe?id=abc123>",

    "List-Unsubscribe-Post": "List-Unsubscribe=One-Click",


    // Custom tracking

    "X-Campaign-ID": "weekly-digest-2026-03",

    "X-User-Segment": "premium",

  },

});


```

Explain Code

## Header limits

| Limit                                  | Value       |
| -------------------------------------- | ----------- |
| Max whitelisted (non-X) custom headers | 20          |
| Max header name length                 | 100 bytes   |
| Max header value length                | 2,048 bytes |
| Total custom headers payload           | 16 KB       |

The total payload is calculated as `sum(len(name) + 2 + len(value) + 2)` for all custom headers (name + `: ` \+ value + CRLF). Whitelisted and X-headers are counted together toward this limit.

## Validation rules

1. **Header names** — ASCII only, no spaces, no colons, 1–100 characters. Whitelisted headers must match `[A-Za-z0-9\-]+`. X-headers must match `X-[A-Za-z0-9\-_]+` (underscores allowed only in X-headers).
2. **Header values** — UTF-8 allowed, maximum 2,048 bytes, no bare CR/LF. Empty values are rejected.
3. **Case-insensitive matching** — Header names are matched case-insensitively per [RFC 5322 §2.2 ↗](https://datatracker.ietf.org/doc/html/rfc5322#section-2.2). The canonical casing from the whitelist is used in the generated message.
4. **Proper line folding** — Long headers are folded at 78 characters per RFC 5322 using CRLF+WSP, not MIME encoding.
5. **Single occurrence** — The `headers` type is `{ [key]: string }`, so each header name can appear at most once. For headers that support multiple values (such as `Keywords` or `Comments`), use comma-separated values in a single string.

## Error codes

| Error Code                  | When                                                         | Example message                                                               |
| --------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------- |
| E\_HEADER\_NOT\_ALLOWED     | Header is platform-controlled or not on the whitelist        | Header 'Date' is not allowed. It is auto-generated by the platform.           |
| E\_HEADER\_USE\_API\_FIELD  | Header corresponds to a first-class API field                | Header 'From' must be set via the 'from' API field, not the 'headers' object. |
| E\_HEADER\_VALUE\_INVALID   | Header value is malformed or empty                           | Header 'List-Unsubscribe' must contain angle-bracket HTTPS or mailto URI(s).  |
| E\_HEADER\_VALUE\_TOO\_LONG | Header value exceeds 2,048 byte limit                        | Header 'X-Campaign-ID' value exceeds 2048 byte limit.                         |
| E\_HEADER\_NAME\_INVALID    | Header name contains invalid characters or exceeds 100 bytes | Header name 'Bad Header!' contains invalid characters.                        |
| E\_HEADERS\_TOO\_LARGE      | Total custom headers payload exceeds 16 KB                   | Total custom headers payload (17.2KB) exceeds 16KB limit.                     |
| E\_HEADERS\_TOO\_MANY       | Too many whitelisted (non-X) custom headers                  | 21 whitelisted headers provided, maximum is 20.                               |

```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/reference/","name":"Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/reference/headers/","name":"Email headers"}}]}
```

---

---
title: Postmaster
description: Reference page with postmaster information for professionals, as well as configuration details for Email Service.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/reference/postmaster.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Postmaster

This page provides technical information about Email Service to professionals who administer email systems, and other email providers.

Here you will find information regarding Email Service, along with best practices, rules, guidelines, troubleshooting tools, as well as configuration details for Email Service.

## Postmaster

### Contact information

The best way to contact us is using our [community forum ↗](https://community.cloudflare.com/new-topic?category=Feedback/Previews%20%26%20Betas&tags=email) or our [Discord server ↗](https://discord.com/invite/cloudflaredev).

### DKIM signature

[DKIM (DomainKeys Identified Mail) ↗](https://en.wikipedia.org/wiki/DomainKeys%5FIdentified%5FMail) ensures that email messages are not altered in transit between the sender and the recipient's SMTP servers through public-key cryptography.

Through this standard, the sender publishes its public key to a domain's DNS once, and then signs the body of each message before it leaves the server. The recipient server reads the message, gets the domain public key from the domain's DNS, and validates the signature to ensure the message was not altered in transit.

Email Service adds DKIM signatures to outgoing emails on behalf of the customer's sending domain to ensure email authenticity and improve deliverability.

Email Sending and Email Routing use separate DKIM selectors. You can find the DKIM keys for your domain by querying the following:

Terminal window

```

# Email Sending DKIM

dig TXT cf-bounce._domainkey.example.com +short


# Email Routing DKIM

dig TXT cf2024-1._domainkey.example.com +short


```

### DMARC enforcing

Email Service supports Domain-based Message Authentication, Reporting & Conformance (DMARC). When sending emails, Email Service will ensure proper SPF and DKIM alignment to pass DMARC authentication. Refer to [dmarc.org ↗](https://dmarc.org/) for more information on this protocol.

It is recommended that all domains implement the DMARC protocol for optimal email deliverability.

### Mail authentication requirement

Cloudflare Email Service ensures all outbound emails are properly authenticated with both SPF and DKIM to maximize deliverability and maintain sender reputation.

### IPv6 support

Email Service supports IPv6 for outbound email delivery. When connecting to recipient SMTP servers, the service will use IPv6 if the recipient supports it (has AAAA records for their MX servers), and fall back to IPv4 if necessary.

### MX and SPF records

When using Email Service for sending emails, no special MX records are required on your domain. However, if you're also using [Email Routing](https://developers.cloudflare.com/email-routing/) for inbound emails, the appropriate MX records will be configured automatically.

For SPF records, Email Service uses `_spf.mx.cloudflare.net`. Email Sending configures SPF on the `cf-bounce` subdomain, while Email Routing configures SPF on the root domain:

```

v=spf1 include:_spf.mx.cloudflare.net ~all


```

### Outbound prefixes

Email Service sends its traffic using both IPv4 and IPv6 prefixes, when supported by the recipient SMTP server.

If you are a postmaster and are having trouble receiving Email Service emails, allow the following outbound IP addresses in your server configuration:

**IPv4**

`104.30.0.0/19`

**IPv6**

`2405:8100:c000::/38`

_Ranges last updated: December 13th, 2023_

### Outbound hostnames

Email Service will use the following outbound domains for the `HELO/EHLO` command:

* `cloudflare-email.net`
* `cloudflare-email.org`
* `cloudflare-email.com`

PTR records (reverse DNS) ensure that each hostname has a corresponding IP. For example:

Terminal window

```

dig a-h.cloudflare-email.net +short


```

```

104.30.0.7


```

Terminal window

```

dig -x 104.30.0.7 +short


```

```

a-h.cloudflare-email.net.


```

### SMTP errors

Email Service provides detailed SMTP error responses to help diagnose delivery issues.

### Realtime Block Lists

Email Service monitors sender reputation and may temporarily delay or block emails from IPs that appear on Realtime Block Lists (RBLs). This helps maintain the service's overall reputation and deliverability.

If you believe your emails are being incorrectly blocked, please contact the RBL maintainer directly or reach out through our support channels.

---

## Configuration details

### Sending domains

To send emails through Email Service, domains must be verified and configured properly. This includes:

* DNS verification of domain ownership
* Proper SPF record configuration
* DKIM key setup (handled automatically)
* Optional DMARC policy configuration

### Rate limiting

Email Service implements rate limiting to prevent abuse and maintain service quality. Rate limits vary based on your Cloudflare plan and sending patterns.

### Content filtering

Email Service includes content filtering to prevent spam and abuse. Emails that don't meet content guidelines may be rejected or delayed.

### Bounce handling

Email Service automatically handles bounces and provides detailed bounce information through:

* Dashboard analytics
* API responses
* Optional webhook notifications

### Suppression lists

Email Service maintains suppression lists to prevent sending to addresses that have bounced or complained. This helps maintain sender reputation and compliance with anti-spam regulations.

---

## Known limitations

Below, you will find information regarding known limitations for Email Service, particularly Email Routing functionality.

### Email address internationalization (EAI)

Email Routing does not support [internationalized email addresses ↗](https://en.wikipedia.org/wiki/International%5Femail). Email Routing only supports [internationalized domain names ↗](https://en.wikipedia.org/wiki/Internationalized%5Fdomain%5Fname).

This means that you can have email addresses with an internationalized domain, but not an internationalized local-part (the first part of your email address, before the @ symbol). Refer to the following examples:

* `info@piñata.es` \- **Supported**
* `piñata@piñata.es` \- **Not supported**

### Non-delivery reports (NDRs)

Email Routing does not forward non-delivery reports to the original sender. This means the sender will not receive a notification indicating that the email did not reach the intended destination.

### Restrictive DMARC policies can make forwarded emails fail

Due to the nature of email forwarding, restrictive DMARC policies might make forwarded emails fail to be delivered. Refer to [dmarc.org ↗](https://dmarc.org/) for more information.

### Sending or replying to an email from your Cloudflare domain

Email Routing does not support sending or replying from your Cloudflare domain. When you reply to emails forwarded by Email Routing, the reply will be sent from your destination address (like `my-name@gmail.com`), not your custom address (like `info@my-company.com`).

### "." is treated as normal characters for custom addresses

The `.` character, which perform special actions in email providers like Gmail, is treated as a normal character on custom addresses.

```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/reference/","name":"Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/reference/postmaster/","name":"Postmaster"}}]}
```

---

---
title: Troubleshoot SPF, DKIM and DMARC
description: Email authentication is critical for successful email delivery. This guide helps you troubleshoot common SPF, DKIM, and DMARC issues with Email Service.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/email-service/reference/troubleshooting.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Troubleshoot SPF, DKIM and DMARC

Email authentication is critical for successful email delivery. This guide helps you troubleshoot common SPF, DKIM, and DMARC issues with Email Service.

## SPF (Sender Policy Framework) issues

### Multiple SPF records

Having multiple SPF records on your domain is not allowed and will prevent Email Service from working properly. If your domain has multiple SPF records:

1. Log in to the Cloudflare dashboard, select your account and domain, then go to **DNS** \> **Records**.  
[ Go to **Records** ](https://dash.cloudflare.com/?to=/:account/:zone/dns/records)
2. Look for multiple TXT records starting with `v=spf1`.
3. Delete the incorrect SPF record.
4. Ensure you have the correct SPF records:  
   * For **Email Routing** (root domain): `v=spf1 include:_spf.mx.cloudflare.net ~all`  
   * For **Email Sending** (`cf-bounce` subdomain): `v=spf1 include:_spf.mx.cloudflare.net ~all`

### Missing SPF record

If emails are being rejected due to SPF failures:

1. Log in to the Cloudflare dashboard, select your account and domain, then go to **DNS** \> **Records**.  
[ Go to **Records** ](https://dash.cloudflare.com/?to=/:account/:zone/dns/records)
2. Add TXT records for the appropriate service:  
   * For **Email Routing**: **Name**: `@` (root domain), **Content**: `v=spf1 include:_spf.mx.cloudflare.net ~all`  
   * For **Email Sending**: **Name**: `cf-bounce`, **Content**: `v=spf1 include:_spf.mx.cloudflare.net ~all`
3. If you already have an SPF record on the root domain, modify it to include `include:_spf.mx.cloudflare.net`

### SPF record syntax errors

Common SPF record syntax issues:

* **Missing version**: SPF records must start with `v=spf1`
* **Multiple includes**: Combine multiple services using separate `include:` statements
* **Too many DNS lookups**: SPF records are limited to 10 DNS lookups total
* **Incorrect all mechanism**: Use `~all` (SoftFail) or `-all` (Fail), not `+all`

**Correct format:**

```

v=spf1 include:_spf.mx.cloudflare.net include:other-service.com ~all


```

### Checking SPF records

Verify your SPF record is configured correctly:

Terminal window

```

dig TXT example.com +short | grep spf


```

Expected result should include:

```

"v=spf1 include:_spf.mx.cloudflare.net ~all"


```

## DKIM (DomainKeys Identified Mail) issues

### Missing DKIM records

Email Service automatically generates DKIM keys for your domain, but the DNS records must be properly configured. Email Sending and Email Routing use separate DKIM selectors:

1. In the [Cloudflare dashboard ↗](https://dash.cloudflare.com/), go to **Compute** \> **Email Service**.
2. Select your domain.
3. Check the **Settings** page for the appropriate service:  
   * **Email Sending**: Go to **Email Sending** \> **Settings** to find the sending DKIM record (`cf-bounce._domainkey`).  
   * **Email Routing**: Go to **Email Routing** \> **Settings** to find the routing DKIM record (`cf2024-1._domainkey`).
4. Copy the DKIM record details.
5. Go to **DNS** \> **Records** and add the DKIM TXT record with the correct selector name and public key.

### DKIM key rotation

If you need to rotate DKIM keys:

1. Contact Cloudflare support to request key rotation.
2. Update your DNS records with the new DKIM key when provided.
3. Monitor email delivery during the transition period.

### Checking DKIM records

Verify your DKIM records are configured correctly:

Terminal window

```

# Check Email Sending DKIM

dig TXT cf-bounce._domainkey.example.com +short


# Check Email Routing DKIM

dig TXT cf2024-1._domainkey.example.com +short


```

Expected result for either:

```

"v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."


```

### DKIM signature validation failures

If DKIM validation is failing:

1. Verify the DKIM record exists in DNS
2. Check that the record name matches the correct selector:  
   * Email Sending: `cf-bounce._domainkey.yourdomain.com`  
   * Email Routing: `cf2024-1._domainkey.yourdomain.com`
3. Ensure there are no extra spaces or characters in the DNS record
4. Wait for DNS propagation (up to 48 hours)
5. Use online DKIM validators to test your configuration

## DMARC (Domain-based Message Authentication, Reporting & Conformance) issues

### Missing DMARC policy

While not required, DMARC significantly improves email deliverability:

1. Go to **DNS** \> **Records** in the Cloudflare dashboard.  
[ Go to **Records** ](https://dash.cloudflare.com/?to=/:account/:zone/dns/records)
2. Add a TXT record:  
   * **Name**: `_dmarc`  
   * **Content**: `v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com`

### DMARC policy too strict

If a strict DMARC policy is causing delivery issues:

1. Start with a lenient policy: `p=none` (monitor only)
2. Monitor DMARC reports for several weeks
3. Gradually increase strictness: `p=quarantine` then `p=reject`
4. Ensure both SPF and DKIM are properly aligned

### DMARC alignment issues

DMARC requires either SPF or DKIM alignment:

**SPF alignment**: The domain in the `Mail From` header must align with the domain in the `From` header**DKIM alignment**: The DKIM signature domain must align with the domain in the `From` header

Email Service ensures proper alignment automatically.

### Checking DMARC records

Verify your DMARC record:

Terminal window

```

dig TXT _dmarc.example.com +short


```

Example result:

```

"v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com; ruf=mailto:dmarc@example.com; sp=quarantine"


```

## Local development issues

### "Cannot serialize value: \[object ArrayBuffer\]"

This error occurs when passing `ArrayBuffer` content in attachment fields during local development with `wrangler dev`. The local email binding simulator cannot serialize `ArrayBuffer` values.

**Solution:** Deploy your Worker with `npx wrangler deploy` and test binary attachments (images, PDFs) against the deployed version. String content for text-based attachments works normally in local development. Refer to [local development for email sending](https://developers.cloudflare.com/email-service/local-development/sending/#known-limitations) for more details.

## Common delivery issues

### Email going to spam

If emails are going to spam folders:

1. Check authentication: Ensure SPF, DKIM, and DMARC are properly configured
2. Domain reputation: New domains may have lower reputation initially
3. Content quality: Avoid spam trigger words and excessive HTML formatting
4. Sender reputation: Monitor bounce rates and complaint rates
5. List hygiene: Remove bounced and invalid email addresses

### High bounce rates

To reduce bounce rates:

1. Validate email addresses: Use real-time validation
2. Maintain clean lists: Remove hard bounces immediately
3. Monitor feedback loops: Subscribe to ISP feedback loops
4. Gradual warm-up: For new domains, start with small volumes

### ISP-specific issues

Different ISPs have specific requirements:

* Gmail: Requires strong domain reputation and authentication
* Outlook/Hotmail: Sensitive to content and sender reputation
* Yahoo: Strict DMARC enforcement
* Corporate: Often have strict filtering rules

## Testing tools

Use these tools to validate your email authentication setup:

1. MX Toolbox: Check SPF, DKIM, and DMARC records
2. DMARC Analyzer: Validate DMARC policy and alignment
3. Mail Tester: Test email deliverability and authentication
4. Google Admin Toolbox: Google's email authentication checker

## Getting help

If you continue to experience authentication issues:

1. Check the [Email Service analytics](https://developers.cloudflare.com/email-service/observability/metrics-analytics/) for delivery metrics
2. Review bounce messages for specific error codes
3. Contact [Cloudflare Support ↗](https://dash.cloudflare.com/?to=/:account/support) with:  
   * Domain name  
   * Example email headers  
   * Specific error messages  
   * SPF, DKIM, and DMARC record configurations

```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/reference/","name":"Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/email-service/reference/troubleshooting/","name":"Troubleshoot SPF, DKIM and DMARC"}}]}
```
