splash image

October 16, 2024

Routing emails through a Cloudflare Worker

This is a quick walkthrough of how to setup email routing on your domain and process emails with a Cloudflare Worker.

The docs and dashboard UI are already really awesome, so I'll be relying on screenshots a lot. 😇

If all you need is to forward emails, you can do this without a worker, but workers are a nice way to handle more complex routing logic or process emails in other ways.

This post assumes that you have a domain name and are using Cloudflare to manage DNS. It also assumes that the domain is not already configured for another email provider.

Oct 27, 2024: Added POST handler which sends email, updated GitHub starter.

1. Enable email routing on your domain

Go to Websites in your Cloudflare dashboard and select the domain for which you're enabling email (I'm using jldec.fun).

Look for Email > Email Routing and click the Get started button. Screenshot of Email Routing page in the Cloudflare Dashboard

Provide a new address for receiving, and a destination to forward to.

Screenshot of Email Routing Getting Started

After verifying the forwarding address, confirm the DNS changes.

Screenshot of Email DNS configuration

Once the DNS changes are done, your first routing rules will take effect.

Screenshot of Email Routing Rules

2. Setup an email Worker

In this step we will add a worker, triggered by incoming email messages.

Go to the Email Workers tab, and cick the Create button.`

Screenshot of the Email Workers tab

Choose the Allowlist senders starter.

Screenshot of Email Workers starters

In the online editor, fix the code to suit your needs, then Save and Deploy.

Screenshot of Email Workers editor

Create a custom email address for the worker to receive emails.

Screenshot of Email Workers custom address

Send a test email to the new worker email address.

Screenshot of Email Workers test email

You should see the Live worker logs in the dashboard for the newly-deployed worker, under Workers & Pages in your account. Start the log stream before sending the email.

Screenshot of Email Workers real time logs

3. Deploy the Worker with Wrangler

wrangler will allow you to configure the worker to persist logs, and can run builds with TypeScript and 3rd-party npm packages.

To make this easier, I created a starter project at github.com/jldec/my-email-worker with logging enabled.

The sample uses postal-mime to parse attachments, and mime-text to generate a reply. The mime-text package requires nodejs_compat in wrangler.toml.

To use this starter:

  1. pnpm create cloudflare --template github:jldec/my-email-worker
  2. Modify the worker address and forwarding address in wrangler.toml.
  3. Deploy the worker with pnpm wranger deploy

wrangler.toml

#:schema node_modules/wrangler/config-schema.json
name = "my-email-worker"
main = "src/index.ts"
compatibility_date = "2024-10-11"
compatibility_flags = [ "nodejs_compat" ]

[observability]
enabled = true

[vars]
EMAIL_WORKER_ADDRESS = "my-email-worker@jldec.fun"
EMAIL_FORWARD_ADDRESS = "jurgen@jldec.me"

index.ts

/**
 * Welcome to Cloudflare Workers!
 *
 * This is a template for an Email Worker: a worker that is triggered by an incoming email.
 * https://developers.cloudflare.com/email-routing/email-workers/
 *
 * - The wrangler development server is not enabled to run email workers locally.
 * - Run `pnpm ship` to publish your worker
 *
 * Bind resources to your worker in `wrangler.toml`. After adding bindings, a type definition for the
 * `Env` object can be regenerated with `pnpm cf-typegen`.
 *
 * Learn more at https://developers.cloudflare.com/workers/
 */

import { EmailMessage } from 'cloudflare:email'
import { createMimeMessage } from 'mimetext'
import PostalMime from 'postal-mime'

export default {
  email: async (message, env, ctx) => {
    console.log(`Received email from ${message.from}`)

    // parse for attachments - see postal-mime for additional options
    // https://github.com/postalsys/postal-mime/tree/master?tab=readme-ov-file#postalmimeparse
    const email = await PostalMime.parse(message.raw)
    email.attachments.forEach((a) => {
      if (a.mimeType === 'application/json') {
        const jsonString = new TextDecoder().decode(a.content)
        const jsonValue = JSON.parse(jsonString)
        console.log(`JSON attachment value:\n${JSON.stringify(jsonValue, null, 2)}`)
      }
    })

    // reply to sender must include in-reply-to with message ID
    // https://developers.cloudflare.com/email-routing/email-workers/reply-email-workers/
    const messageId = message.headers.get('message-id')
    if (messageId) {
      console.log(`Replying to ${message.from} with message ID ${messageId}`)
      const msg = createMimeMessage()
      msg.setHeader('in-reply-to', messageId)
      msg.setSender(env.EMAIL_WORKER_ADDRESS)
      msg.setRecipient(message.from)
      msg.setSubject('Auto-reply')
      msg.addMessage({
        contentType: 'text/plain',
        data: `Thanks for the message`
      })
      const replyMessage = new EmailMessage(env.EMAIL_WORKER_ADDRESS, message.from, msg.asRaw())
      ctx.waitUntil(message.reply(replyMessage))
    }

    ctx.waitUntil(message.forward(env.EMAIL_FORWARD_ADDRESS))
  }
} satisfies ExportedHandler<Env>

Here are the persisted logs in the Cloudflare dashboard. 🎉

Screenshot of Email Workers logs

Tip: If you use gmail to test forwarding, I suggest using one account to send, and a different account to receive the forwarded emails . Using the same account (even with an alias) has not worked for me.

4. Add POST handler to send emails

For a worker to send emails from a fetch handler, you need a send_email binding in wrangler.toml. E.g.

[[send_email]]
name = "SEND_EMAIL"

The binding name is required to expose env.<NAME>.send() in the worker.

Additional binding values for destination_address or allowed_destination_addresses are optional.

Run wrangler types to add the new binding to the Env interface in worker-configuration.d.ts.

For the binding to work, the from address must match a configured custom address, and the to address must match a configured destination address. The docs are little unclear about this, but this is how I got it to work.

  // Send email in respose to a POST request
  // TODO: CSRF protection, CORS headers, handle form data encoding
  // https://developers.cloudflare.com/email-routing/email-workers/send-email-workers/#example-worker
  async fetch(request, env) {
    if (request.method !== 'POST') {
      return new Response('Method Not Allowed', { status: 405 })
    }
    const msg = createMimeMessage()
    msg.setSender(env.EMAIL_WORKER_ADDRESS)
    msg.setRecipient(env.EMAIL_FORWARD_ADDRESS)
    msg.setSubject('Worker POST')
    msg.addMessage({
      contentType: 'text/plain',
      data: (await request.text()) ?? 'No body'
    })

    var message = new EmailMessage(env.EMAIL_WORKER_ADDRESS, env.EMAIL_FORWARD_ADDRESS, msg.asRaw())
    try {
      await env.SEND_EMAIL.send(message)
    } catch (e) {
      return new Response((e as Error).message)
    }

    return new Response('OK')
  }

Test with a curl request and look for the email in your inbox.

$ curl https://my-email-worker.jldec.workers.dev/ -d 'hello worker'
OK

Screenshot of email sent from worker

💌 You've got mail.

debug

user: anonymous

{
  "path": "/blog/routing-emails-through-a-cloudflare-worker",
  "attrs": {
    "title": "Routing emails through a Cloudflare Worker",
    "date": "2024-10-16",
    "layout": "BlogPostLayout",
    "splash": {
      "image": "/images/red-flower.webp"
    }
  },
  "md": "# Routing emails through a Cloudflare Worker\n\nThis is a quick walkthrough of how to setup [email routing](https://developers.cloudflare.com/email-routing/) on your domain and process emails with a Cloudflare Worker.\n\nThe [docs](https://developers.cloudflare.com/email-routing/) and dashboard UI are already really awesome, so I'll be relying on screenshots a lot. 😇\n\nIf all you need is to forward emails, you can do this without a worker, but workers are a nice way to handle more complex routing logic or process emails in other ways.\n\nThis post assumes that you have a domain name and are using Cloudflare to manage [DNS](https://www.cloudflare.com/learning/dns/dns-records/). It also assumes that the domain is not already configured for another email provider.\n\n_Oct 27, 2024:_ Added POST handler which sends email, [updated](https://github.com/jldec/my-email-worker/pull/4/files) GitHub starter.\n\n## 1. Enable email routing on your domain\n\nGo to `Websites` in your Cloudflare [dashboard](https://dash.cloudflare.com/zones) and select the domain for which you're enabling email (I'm using `jldec.fun`).\n\nLook for `Email > Email Routing` and click the `Get started` button.\n![Screenshot of Email Routing page in the Cloudflare Dashboard](/images/cf-email-dashboard.webp)\n\nProvide a new address for receiving, and a destination to forward to.\n\n![Screenshot of Email Routing Getting Started](/images/cf-email-forward-address.webp)\n\nAfter verifying the forwarding address, confirm the DNS changes.\n\n![Screenshot of Email DNS configuration](/images/cf-email-dns.webp)\n\nOnce the DNS changes are done, your first routing rules will take effect.\n\n![Screenshot of Email Routing Rules](/images/cf-email-routing-rules.webp)\n\n## 2. Setup an email Worker\n\nIn this step we will add a worker, triggered by incoming email messages.\n\nGo to the `Email Workers` tab, and cick the `Create` button.`\n\n![Screenshot of the Email Workers tab](/images/cf-email-workers-create.webp)\n\nChoose the `Allowlist senders` starter.\n\n![Screenshot of Email Workers starters](/images/cf-email-workers-create-templates.webp)\n\nIn the online editor, fix the code to suit your needs, then `Save and Deploy`.\n\n![Screenshot of Email Workers editor](/images/cf-email-workers-create-edit.webp)\n\nCreate a custom email address for the worker to receive emails.\n\n![Screenshot of Email Workers custom address](/images/cf-email-worker-address.webp)\n\nSend a test email to the new worker email address.\n\n![Screenshot of Email Workers test email](/images/cf-email-test-email.webp)\n\nYou should see the `Live` worker logs in the dashboard for the newly-deployed worker, under `Workers & Pages` in your account. Start the log stream before sending the email.\n\n![Screenshot of Email Workers real time logs](/images/cf-email-worker-real-time-logs.webp)\n\n## 3. Deploy the Worker with Wrangler\n\n[wrangler](https://developers.cloudflare.com/workers/wrangler/) will allow you to configure the worker to persist logs, and can run builds with TypeScript and 3rd-party npm packages.\n\nTo make this easier, I created a starter project at [github.com/jldec/my-email-worker](https://github.com/jldec/my-email-worker) with logging enabled.\n\nThe sample uses [postal-mime](https://github.com/postalsys/postal-mime#readme) to parse attachments, and [mime-text](https://github.com/muratgozel/MIMEText) to generate a reply. The `mime-text` package requires `nodejs_compat` in wrangler.toml.\n\nTo use this starter:\n\n1. `pnpm create cloudflare --template github:jldec/my-email-worker`\n2. Modify the worker address and forwarding address in `wrangler.toml`.\n3. Deploy the worker with `pnpm wranger deploy`\n\n**wrangler.toml**\n```toml\n#:schema node_modules/wrangler/config-schema.json\nname = \"my-email-worker\"\nmain = \"src/index.ts\"\ncompatibility_date = \"2024-10-11\"\ncompatibility_flags = [ \"nodejs_compat\" ]\n\n[observability]\nenabled = true\n\n[vars]\nEMAIL_WORKER_ADDRESS = \"my-email-worker@jldec.fun\"\nEMAIL_FORWARD_ADDRESS = \"jurgen@jldec.me\"\n```\n\n**index.ts**\n```ts\n/**\n * Welcome to Cloudflare Workers!\n *\n * This is a template for an Email Worker: a worker that is triggered by an incoming email.\n * https://developers.cloudflare.com/email-routing/email-workers/\n *\n * - The wrangler development server is not enabled to run email workers locally.\n * - Run `pnpm ship` to publish your worker\n *\n * Bind resources to your worker in `wrangler.toml`. After adding bindings, a type definition for the\n * `Env` object can be regenerated with `pnpm cf-typegen`.\n *\n * Learn more at https://developers.cloudflare.com/workers/\n */\n\nimport { EmailMessage } from 'cloudflare:email'\nimport { createMimeMessage } from 'mimetext'\nimport PostalMime from 'postal-mime'\n\nexport default {\n  email: async (message, env, ctx) => {\n    console.log(`Received email from ${message.from}`)\n\n    // parse for attachments - see postal-mime for additional options\n    // https://github.com/postalsys/postal-mime/tree/master?tab=readme-ov-file#postalmimeparse\n    const email = await PostalMime.parse(message.raw)\n    email.attachments.forEach((a) => {\n      if (a.mimeType === 'application/json') {\n        const jsonString = new TextDecoder().decode(a.content)\n        const jsonValue = JSON.parse(jsonString)\n        console.log(`JSON attachment value:\\n${JSON.stringify(jsonValue, null, 2)}`)\n      }\n    })\n\n    // reply to sender must include in-reply-to with message ID\n    // https://developers.cloudflare.com/email-routing/email-workers/reply-email-workers/\n    const messageId = message.headers.get('message-id')\n    if (messageId) {\n      console.log(`Replying to ${message.from} with message ID ${messageId}`)\n      const msg = createMimeMessage()\n      msg.setHeader('in-reply-to', messageId)\n      msg.setSender(env.EMAIL_WORKER_ADDRESS)\n      msg.setRecipient(message.from)\n      msg.setSubject('Auto-reply')\n      msg.addMessage({\n        contentType: 'text/plain',\n        data: `Thanks for the message`\n      })\n      const replyMessage = new EmailMessage(env.EMAIL_WORKER_ADDRESS, message.from, msg.asRaw())\n      ctx.waitUntil(message.reply(replyMessage))\n    }\n\n    ctx.waitUntil(message.forward(env.EMAIL_FORWARD_ADDRESS))\n  }\n} satisfies ExportedHandler<Env>\n```\n\nHere are the persisted logs in the Cloudflare dashboard. 🎉\n\n![Screenshot of Email Workers logs](/images/cf-email-worker-persisted-logs.webp)\n\n> Tip: If you use gmail to test forwarding, I suggest using one account to send, and a different account to receive the forwarded emails . Using the same account (even with an alias) has not worked for me.\n\n## 4. Add POST handler to send emails\n\nFor a worker to send emails from a fetch handler, you need a `send_email` [binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/#what-is-a-binding) in `wrangler.toml`. E.g.\n\n```toml\n[[send_email]]\nname = \"SEND_EMAIL\"\n```\n\nThe binding `name` is required to expose `env.<NAME>.send()` in the worker.\n\nAdditional binding [values](https://developers.cloudflare.com/email-routing/email-workers/send-email-workers/#types-of-bindings) for `destination_address` or `allowed_destination_addresses` are optional.\n\nRun `wrangler types` to add the new binding to the `Env` interface in `worker-configuration.d.ts`.\n\nFor the binding to work, the `from` address must match a configured [custom address](https://developers.cloudflare.com/email-routing/setup/email-routing-addresses/#custom-addresses), and the `to` address must match a configured [destination address](https://developers.cloudflare.com/email-routing/setup/email-routing-addresses/#destination-addresses). The [docs](https://developers.cloudflare.com/email-routing/email-workers/send-email-workers/) are little unclear about this, but this is how I got it to work.\n\n```ts\n  // Send email in respose to a POST request\n  // TODO: CSRF protection, CORS headers, handle form data encoding\n  // https://developers.cloudflare.com/email-routing/email-workers/send-email-workers/#example-worker\n  async fetch(request, env) {\n    if (request.method !== 'POST') {\n      return new Response('Method Not Allowed', { status: 405 })\n    }\n    const msg = createMimeMessage()\n    msg.setSender(env.EMAIL_WORKER_ADDRESS)\n    msg.setRecipient(env.EMAIL_FORWARD_ADDRESS)\n    msg.setSubject('Worker POST')\n    msg.addMessage({\n      contentType: 'text/plain',\n      data: (await request.text()) ?? 'No body'\n    })\n\n    var message = new EmailMessage(env.EMAIL_WORKER_ADDRESS, env.EMAIL_FORWARD_ADDRESS, msg.asRaw())\n    try {\n      await env.SEND_EMAIL.send(message)\n    } catch (e) {\n      return new Response((e as Error).message)\n    }\n\n    return new Response('OK')\n  }\n```\n\nTest with a curl request and look for the email in your inbox.\n```sh\n$ curl https://my-email-worker.jldec.workers.dev/ -d 'hello worker'\nOK\n```\n![Screenshot of email sent from worker](/images/cf-email-worker-inbox.webp)\n\n> 💌 You've got mail.\n",
  "html": "<h1>Routing emails through a Cloudflare Worker</h1>\n<p>This is a quick walkthrough of how to setup <a href=\"https://developers.cloudflare.com/email-routing/\">email routing</a> on your domain and process emails with a Cloudflare Worker.</p>\n<p>The <a href=\"https://developers.cloudflare.com/email-routing/\">docs</a> and dashboard UI are already really awesome, so I'll be relying on screenshots a lot. 😇</p>\n<p>If all you need is to forward emails, you can do this without a worker, but workers are a nice way to handle more complex routing logic or process emails in other ways.</p>\n<p>This post assumes that you have a domain name and are using Cloudflare to manage <a href=\"https://www.cloudflare.com/learning/dns/dns-records/\">DNS</a>. It also assumes that the domain is not already configured for another email provider.</p>\n<p><em>Oct 27, 2024:</em> Added POST handler which sends email, <a href=\"https://github.com/jldec/my-email-worker/pull/4/files\">updated</a> GitHub starter.</p>\n<h2>1. Enable email routing on your domain</h2>\n<p>Go to <code>Websites</code> in your Cloudflare <a href=\"https://dash.cloudflare.com/zones\">dashboard</a> and select the domain for which you're enabling email (I'm using <code>jldec.fun</code>).</p>\n<p>Look for <code>Email &gt; Email Routing</code> and click the <code>Get started</code> button.\n<img src=\"/images/cf-email-dashboard.webp\" alt=\"Screenshot of Email Routing page in the Cloudflare Dashboard\"></p>\n<p>Provide a new address for receiving, and a destination to forward to.</p>\n<p><img src=\"/images/cf-email-forward-address.webp\" alt=\"Screenshot of Email Routing Getting Started\"></p>\n<p>After verifying the forwarding address, confirm the DNS changes.</p>\n<p><img src=\"/images/cf-email-dns.webp\" alt=\"Screenshot of Email DNS configuration\"></p>\n<p>Once the DNS changes are done, your first routing rules will take effect.</p>\n<p><img src=\"/images/cf-email-routing-rules.webp\" alt=\"Screenshot of Email Routing Rules\"></p>\n<h2>2. Setup an email Worker</h2>\n<p>In this step we will add a worker, triggered by incoming email messages.</p>\n<p>Go to the <code>Email Workers</code> tab, and cick the <code>Create</code> button.`</p>\n<p><img src=\"/images/cf-email-workers-create.webp\" alt=\"Screenshot of the Email Workers tab\"></p>\n<p>Choose the <code>Allowlist senders</code> starter.</p>\n<p><img src=\"/images/cf-email-workers-create-templates.webp\" alt=\"Screenshot of Email Workers starters\"></p>\n<p>In the online editor, fix the code to suit your needs, then <code>Save and Deploy</code>.</p>\n<p><img src=\"/images/cf-email-workers-create-edit.webp\" alt=\"Screenshot of Email Workers editor\"></p>\n<p>Create a custom email address for the worker to receive emails.</p>\n<p><img src=\"/images/cf-email-worker-address.webp\" alt=\"Screenshot of Email Workers custom address\"></p>\n<p>Send a test email to the new worker email address.</p>\n<p><img src=\"/images/cf-email-test-email.webp\" alt=\"Screenshot of Email Workers test email\"></p>\n<p>You should see the <code>Live</code> worker logs in the dashboard for the newly-deployed worker, under <code>Workers &amp; Pages</code> in your account. Start the log stream before sending the email.</p>\n<p><img src=\"/images/cf-email-worker-real-time-logs.webp\" alt=\"Screenshot of Email Workers real time logs\"></p>\n<h2>3. Deploy the Worker with Wrangler</h2>\n<p><a href=\"https://developers.cloudflare.com/workers/wrangler/\">wrangler</a> will allow you to configure the worker to persist logs, and can run builds with TypeScript and 3rd-party npm packages.</p>\n<p>To make this easier, I created a starter project at <a href=\"https://github.com/jldec/my-email-worker\">github.com/jldec/my-email-worker</a> with logging enabled.</p>\n<p>The sample uses <a href=\"https://github.com/postalsys/postal-mime#readme\">postal-mime</a> to parse attachments, and <a href=\"https://github.com/muratgozel/MIMEText\">mime-text</a> to generate a reply. The <code>mime-text</code> package requires <code>nodejs_compat</code> in wrangler.toml.</p>\n<p>To use this starter:</p>\n<ol>\n<li><code>pnpm create cloudflare --template github:jldec/my-email-worker</code></li>\n<li>Modify the worker address and forwarding address in <code>wrangler.toml</code>.</li>\n<li>Deploy the worker with <code>pnpm wranger deploy</code></li>\n</ol>\n<p><strong>wrangler.toml</strong></p>\n<pre><code class=\"language-toml\">#:schema node_modules/wrangler/config-schema.json\nname = &quot;my-email-worker&quot;\nmain = &quot;src/index.ts&quot;\ncompatibility_date = &quot;2024-10-11&quot;\ncompatibility_flags = [ &quot;nodejs_compat&quot; ]\n\n[observability]\nenabled = true\n\n[vars]\nEMAIL_WORKER_ADDRESS = &quot;my-email-worker@jldec.fun&quot;\nEMAIL_FORWARD_ADDRESS = &quot;jurgen@jldec.me&quot;\n</code></pre>\n<p><strong>index.ts</strong></p>\n<pre><code class=\"language-ts\">/**\n * Welcome to Cloudflare Workers!\n *\n * This is a template for an Email Worker: a worker that is triggered by an incoming email.\n * https://developers.cloudflare.com/email-routing/email-workers/\n *\n * - The wrangler development server is not enabled to run email workers locally.\n * - Run `pnpm ship` to publish your worker\n *\n * Bind resources to your worker in `wrangler.toml`. After adding bindings, a type definition for the\n * `Env` object can be regenerated with `pnpm cf-typegen`.\n *\n * Learn more at https://developers.cloudflare.com/workers/\n */\n\nimport { EmailMessage } from 'cloudflare:email'\nimport { createMimeMessage } from 'mimetext'\nimport PostalMime from 'postal-mime'\n\nexport default {\n  email: async (message, env, ctx) =&gt; {\n    console.log(`Received email from ${message.from}`)\n\n    // parse for attachments - see postal-mime for additional options\n    // https://github.com/postalsys/postal-mime/tree/master?tab=readme-ov-file#postalmimeparse\n    const email = await PostalMime.parse(message.raw)\n    email.attachments.forEach((a) =&gt; {\n      if (a.mimeType === 'application/json') {\n        const jsonString = new TextDecoder().decode(a.content)\n        const jsonValue = JSON.parse(jsonString)\n        console.log(`JSON attachment value:\\n${JSON.stringify(jsonValue, null, 2)}`)\n      }\n    })\n\n    // reply to sender must include in-reply-to with message ID\n    // https://developers.cloudflare.com/email-routing/email-workers/reply-email-workers/\n    const messageId = message.headers.get('message-id')\n    if (messageId) {\n      console.log(`Replying to ${message.from} with message ID ${messageId}`)\n      const msg = createMimeMessage()\n      msg.setHeader('in-reply-to', messageId)\n      msg.setSender(env.EMAIL_WORKER_ADDRESS)\n      msg.setRecipient(message.from)\n      msg.setSubject('Auto-reply')\n      msg.addMessage({\n        contentType: 'text/plain',\n        data: `Thanks for the message`\n      })\n      const replyMessage = new EmailMessage(env.EMAIL_WORKER_ADDRESS, message.from, msg.asRaw())\n      ctx.waitUntil(message.reply(replyMessage))\n    }\n\n    ctx.waitUntil(message.forward(env.EMAIL_FORWARD_ADDRESS))\n  }\n} satisfies ExportedHandler&lt;Env&gt;\n</code></pre>\n<p>Here are the persisted logs in the Cloudflare dashboard. 🎉</p>\n<p><img src=\"/images/cf-email-worker-persisted-logs.webp\" alt=\"Screenshot of Email Workers logs\"></p>\n<blockquote>\n<p>Tip: If you use gmail to test forwarding, I suggest using one account to send, and a different account to receive the forwarded emails . Using the same account (even with an alias) has not worked for me.</p>\n</blockquote>\n<h2>4. Add POST handler to send emails</h2>\n<p>For a worker to send emails from a fetch handler, you need a <code>send_email</code> <a href=\"https://developers.cloudflare.com/workers/runtime-apis/bindings/#what-is-a-binding\">binding</a> in <code>wrangler.toml</code>. E.g.</p>\n<pre><code class=\"language-toml\">[[send_email]]\nname = &quot;SEND_EMAIL&quot;\n</code></pre>\n<p>The binding <code>name</code> is required to expose <code>env.&lt;NAME&gt;.send()</code> in the worker.</p>\n<p>Additional binding <a href=\"https://developers.cloudflare.com/email-routing/email-workers/send-email-workers/#types-of-bindings\">values</a> for <code>destination_address</code> or <code>allowed_destination_addresses</code> are optional.</p>\n<p>Run <code>wrangler types</code> to add the new binding to the <code>Env</code> interface in <code>worker-configuration.d.ts</code>.</p>\n<p>For the binding to work, the <code>from</code> address must match a configured <a href=\"https://developers.cloudflare.com/email-routing/setup/email-routing-addresses/#custom-addresses\">custom address</a>, and the <code>to</code> address must match a configured <a href=\"https://developers.cloudflare.com/email-routing/setup/email-routing-addresses/#destination-addresses\">destination address</a>. The <a href=\"https://developers.cloudflare.com/email-routing/email-workers/send-email-workers/\">docs</a> are little unclear about this, but this is how I got it to work.</p>\n<pre><code class=\"language-ts\">  // Send email in respose to a POST request\n  // TODO: CSRF protection, CORS headers, handle form data encoding\n  // https://developers.cloudflare.com/email-routing/email-workers/send-email-workers/#example-worker\n  async fetch(request, env) {\n    if (request.method !== 'POST') {\n      return new Response('Method Not Allowed', { status: 405 })\n    }\n    const msg = createMimeMessage()\n    msg.setSender(env.EMAIL_WORKER_ADDRESS)\n    msg.setRecipient(env.EMAIL_FORWARD_ADDRESS)\n    msg.setSubject('Worker POST')\n    msg.addMessage({\n      contentType: 'text/plain',\n      data: (await request.text()) ?? 'No body'\n    })\n\n    var message = new EmailMessage(env.EMAIL_WORKER_ADDRESS, env.EMAIL_FORWARD_ADDRESS, msg.asRaw())\n    try {\n      await env.SEND_EMAIL.send(message)\n    } catch (e) {\n      return new Response((e as Error).message)\n    }\n\n    return new Response('OK')\n  }\n</code></pre>\n<p>Test with a curl request and look for the email in your inbox.</p>\n<pre><code class=\"language-sh\">$ curl https://my-email-worker.jldec.workers.dev/ -d 'hello worker'\nOK\n</code></pre>\n<p><img src=\"/images/cf-email-worker-inbox.webp\" alt=\"Screenshot of email sent from worker\"></p>\n<blockquote>\n<p>💌 You've got mail.</p>\n</blockquote>\n"
}