Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e0b5df5
Email Workers local development
celso Apr 4, 2025
40d3dd8
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 4, 2025
ce3c1a2
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 4, 2025
2347699
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 4, 2025
160128a
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 4, 2025
f3184c6
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 4, 2025
1235570
preview link no longer required
celso Apr 4, 2025
8441c2d
changelog entry
celso Apr 4, 2025
114241b
changes required
celso Apr 4, 2025
9a154dd
typo
celso Apr 7, 2025
966a3ba
closes code section
celso Apr 7, 2025
fbc3ab6
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 7, 2025
154ebf1
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 7, 2025
7409620
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 7, 2025
a238063
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 7, 2025
c0f84a4
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 7, 2025
3538283
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 7, 2025
75de507
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 7, 2025
32662f3
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 7, 2025
eb91077
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 7, 2025
14621f6
Update src/content/docs/email-routing/email-workers/local-development…
celso Apr 7, 2025
c3bc5cc
update changelog
celso Apr 8, 2025
3fb0a90
Merge branch 'celso/email-localdev' of github.com:cloudflare/cloudfla…
celso Apr 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
title: Local development support for Email Workers
description: Developers can now use wrangler to do local development for Email Workers.
date: 2025-04-04T17:00:00Z
---

Email Workers enables developers to programmatically take action on anything that hits their email inbox. If you're building with Email Workers, you can now test the behavior of an Email Worker script, receiving, replying and sending emails in your local environment using `wrangler dev`.

Below is an example that shows you how you can receive messages using the `email()` handler and parse them using [postal-mime](https://www.npmjs.com/package/postal-mime):

```ts
import * as PostalMime from 'postal-mime';

export default {
async email(message, env, ctx) {
const parser = new PostalMime.default();
const rawEmail = new Response(message.raw);
const email = await parser.parse(await rawEmail.arrayBuffer());
console.log(email);
},
};

Now when you run `npx wrangler dev`, wrangler will expose a local `/cdn-cgi/handler/email` endpoint that you can `POST` email messages to and trigger your Worker's `email()` handler:

```bash
curl -X POST 'http://localhost:8787/cdn-cgi/handler/email' \
--url-query '[email protected]' \
--url-query '[email protected]' \
--header 'Content-Type: application/json' \
--data-raw 'Received: from smtp.example.com (127.0.0.1)
by cloudflare-email.com (unknown) id 4fwwffRXOpyR
for <[email protected]>; Tue, 27 Aug 2024 15:50:20 +0000
From: "John" <[email protected]>
Reply-To: [email protected]
To: [email protected]
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'
```

This is what you get in the console:

```json
{
headers: [
{
key: 'received',
value: 'from smtp.example.com (127.0.0.1) by cloudflare-email.com (unknown) id 4fwwffRXOpyR for <[email protected]>; Tue, 27 Aug 2024 15:50:20 +0000'
},
{ key: 'from', value: '"John" <[email protected]>' },
{ key: 'reply-to', value: '[email protected]' },
{ key: 'to', value: '[email protected]' },
{ 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: '[email protected]', name: 'John' },
to: [ { address: '[email protected]', name: '' } ],
replyTo: [ { address: '[email protected]', name: '' } ],
subject: 'Testing Email Workers Local Dev',
messageId: '<6114391943504294873000@ZSH-GHOSTTY>',
date: '2024-08-27T15:49:44.000Z',
html: 'Hi there\n',
attachments: []
}
```

Local development is a critical part of the development flow, and also works for sending, replying and forwarding emails. See [our documentation](/email-routing/email-workers/local-development/) for more information.
4 changes: 2 additions & 2 deletions src/content/docs/email-routing/email-workers/demos.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pcx_content_type: navigation
title: Demos
sidebar:
order: 6
order: 7

---

Expand All @@ -14,4 +14,4 @@ Learn how you can use Email Workers within your existing architecture.

Explore the following <GlossaryTooltip term="demo application">demo applications</GlossaryTooltip> for Email Workers.

<ExternalResources type="apps" products={["Email Workers"]} />
<ExternalResources type="apps" products={["Email Workers"]} />
205 changes: 205 additions & 0 deletions src/content/docs/email-routing/email-workers/local-development.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
---
title: Local Development
sidebar:
order: 6
badge: Beta

---

import { Render, Type, MetaInfo, WranglerConfig } from "~/components";

You can test the behavior of an Email Worker script in local development using [wrangler dev](/workers/wrangler/commands/#dev).

This is the minimal wrangler configuration required to run an Email Worker locally:

<WranglerConfig>

```jsonc
{
"send_email": [
{
"name": "EMAIL"
}
]
}
```

</WranglerConfig>

:::note

If you want to deploy your script you need to enable Email Routing [enabled](/email-routing/get-started/enable-email-routing/) and have at least one verified [destination address](/email-routing/setup/email-routing-addresses/#destination-addresses).

:::

You can now test receiving, replying and sending emails in your local environment.

## Receiving an email

Consider this example Email Worker script that uses the opensource [postal-mime](https://www.npmjs.com/package/postal-mime) email parser:

```ts
import * as PostalMime from 'postal-mime';

export default {
async email(message, env, ctx) {
const parser = new PostalMime.default();
const rawEmail = new Response(message.raw);
const email = await parser.parse(await rawEmail.arrayBuffer());
console.log(email);
},
};

Now when you run `npx wrangler dev`, wrangler will expose a local `/cdn-cgi/handler/email` endpoint that you can `POST` email messages to and trigger your Worker's `email()` handler:

```bash
curl -X POST 'http://localhost:8787/cdn-cgi/handler/email' \
--url-query '[email protected]' \
--url-query '[email protected]' \
--header 'Content-Type: application/json' \
--data-raw 'Received: from smtp.example.com (127.0.0.1)
by cloudflare-email.com (unknown) id 4fwwffRXOpyR
for <[email protected]>; Tue, 27 Aug 2024 15:50:20 +0000
From: "John" <[email protected]>
Reply-To: [email protected]
To: [email protected]
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'
```

This is what you get in the console:

```json
{
headers: [
{
key: 'received',
value: 'from smtp.example.com (127.0.0.1) by cloudflare-email.com (unknown) id 4fwwffRXOpyR for <[email protected]>; Tue, 27 Aug 2024 15:50:20 +0000'
},
{ key: 'from', value: '"John" <[email protected]>' },
{ key: 'reply-to', value: '[email protected]' },
{ key: 'to', value: '[email protected]' },
{ 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: '[email protected]', name: 'John' },
to: [ { address: '[email protected]', name: '' } ],
replyTo: [ { address: '[email protected]', name: '' } ],
subject: 'Testing Email Workers Local Dev',
messageId: '<6114391943504294873000@ZSH-GHOSTTY>',
date: '2024-08-27T15:49:44.000Z',
html: 'Hi there\n',
attachments: []
}
```

## Sending an email

Wrangler can also simulate sending emails locally. Consider this example Email Worker script that uses the [mimetext](https://www.npmjs.com/package/mimetext) npm package:

```ts
import { EmailMessage } from "cloudflare:email";
import { createMimeMessage } from 'mimetext';

export default {
async fetch(request, env, ctx) {
const msg = createMimeMessage();
msg.setSender({ name: 'Sending email test', addr: '[email protected]' });
msg.setRecipient('[email protected]');
msg.setSubject('An email generated in a worker');
msg.addMessage({
contentType: 'text/plain',
data: `Congratulations, you just sent an email from a worker.`,
});

var message = new EmailMessage('[email protected]', '[email protected]', msg.asRaw());
await env.EMAIL.send(message);
return Response.json({ ok: true });
}
};
```

Now when you run `npx wrangler dev`, go to http://localhost:8787/ to trigger the `fetch()` handler and send the email. You will see the follow message in your terminal:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Now when you run `npx wrangler dev`, go to http://localhost:8787/ to trigger the `fetch()` handler and send the email. You will see the follow message in your terminal:
Now when you run `npx wrangler dev`, go to http://localhost:8787/ to trigger the `fetch()` handler and send the email. Your terminal will show the following:

Let's avoid "see" here.


```bash
⎔ Starting local server...
[wrangler:inf] Ready on http://localhost:8787
[wrangler:inf] GET / 200 OK (19ms)
[wrangler:inf] send_email binding called with the following message:
/var/folders/33/pn86qymd0w50htvsjp93rys40000gn/T/miniflare-f9be031ff417b2e67f2ac4cf94cb1b40/files/email/33e0a255-a7df-4f40-b712-0291806ed2b3.eml
```

Wrangler simulated `env.EMAIL.send()` by writing the email to a local file in [eml](https://datatracker.ietf.org/doc/html/rfc5322) format. The file contains the raw email message:

```
Date: Fri, 04 Apr 2025 12:27:08 +0000
From: =?utf-8?B?U2VuZGluZyBlbWFpbCB0ZXN0?= <[email protected]>
To: <[email protected]>
Message-ID: <[email protected]>
Subject: =?utf-8?B?QW4gZW1haWwgZ2VuZXJhdGVkIGluIGEgd29ya2Vy?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit

Congratulations, you just sent an email from a worker.
```

## Replying to and forwarding messages

Likewise, [`EmailMessage`](/email-routing/email-workers/runtime-api/#emailmessage-definition)'s `forward()` and `reply()` methods are also simulated locally. Consider this Worker that receives an email, parses it, replies to the sender, and forwards the original message to one your verified recipient addresses:

```ts
import * as PostalMime from 'postal-mime';
import { createMimeMessage } from 'mimetext';
import { EmailMessage } from 'cloudflare:email';

export default {
async email(message, env: any, ctx: any) {
// parses incoming message
const parser = new PostalMime.default();
const rawEmail = new Response(message.raw);
const email = await parser.parse(await rawEmail.arrayBuffer());

// creates some ticket
// const ticket = await createTicket(email);

// creates reply message
const msg = createMimeMessage();
msg.setSender({ name: 'Thank you for your contact', addr: '[email protected]' });
msg.setRecipient(message.from);
msg.setHeader('In-Reply-To', message.headers.get('Message-ID'));
msg.setSubject('An email generated in a worker');
msg.addMessage({
contentType: 'text/plain',
data: `This is an automated reply. We received you email with the subject "${email.subject}", and will handle it as soon as possible.`,
});

const replyMessage = new EmailMessage('[email protected]', message.from, msg.asRaw());

await message.reply(replyMessage);
await message.forward("[email protected]");
},
};
```

Run `npx wrangler dev` and use curl to POST the same message from the [Receiving an email](#receiving-an-email) example. Your terminal will show you where to find the replied message in your local disk and to whom the email was forwarded:

```bash
⎔ Starting local server...
[wrangler:inf] Ready on http://localhost:8787
[wrangler:inf] Email handler replied to sender with the following message:
/var/folders/33/pn86qymd0w50htvsjp93rys40000gn/T/miniflare-381a79d7efa4e991607b30a079f6b17d/files/email/a1db7ebb-ccb4-45ef-b315-df49c6d820c0.eml
[wrangler:inf] Email handler forwarded message with
rcptTo: [email protected]
```
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ An `EmailEvent` is the event type to programmatically process your emails with a
`EmailEvent` can be handled in Workers functions written using the Service Worker syntax by attaching to the `email` event with `addEventListener`:

```js
addEventListener("email", (event) => {
event.message.forward("<YOUR_EMAIL>");
addEventListener("email", async (event) => {
await event.message.forward("<YOUR_EMAIL>");
});
```

Expand All @@ -41,7 +41,7 @@ addEventListener("email", (event) => {
```js
export default {
async email(message, env, ctx) {
message.forward("<YOUR_EMAIL>");
await message.forward("<YOUR_EMAIL>");
},
};
```
Expand Down Expand Up @@ -118,4 +118,3 @@ export default {

* Reply to the sender of this email message with a new EmailMessage object.
* When the promise resolves, the message is confirmed to be replied.

2 changes: 1 addition & 1 deletion src/content/glossary/fundamentals.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ entries:
- term: protocol
general_definition: |-
a protocol is a set of rules governing the exchange or transmission of data between devices.

- term: proxy server
general_definition: |-
the server that sits between the origin server and the client. Cloudflare is a proxy server for example.
Expand Down
2 changes: 1 addition & 1 deletion src/content/learning-paths/china-network-overview.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"title": "Introduction to the China Network",
"path": "/learning-paths/china-network-overview/series/china-network-main-features-1/",
"priority": 1,
"description":"Watch to learn how Cloudflare's China Network can help you improve performance, compliance, and connectivity for your users in mainland China.",
"description": "Watch to learn how Cloudflare's China Network can help you improve performance, compliance, and connectivity for your users in mainland China.",
"products": ["China Network"],
"product_group": "Application performance",
"additional_groups": ["Application security"],
Expand Down
2 changes: 1 addition & 1 deletion src/content/learning-paths/durable-objects-course.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"title": "Introduction to Durable Objects",
"path": "/learning-paths/durable-objects-course/series/introduction-to-series-1/",
"priority": 2,
"description":"Dive into a hands-on Durable Objects project and learn how to build stateful apps using serverless architecture",
"description": "Dive into a hands-on Durable Objects project and learn how to build stateful apps using serverless architecture",
"products": ["Durable Objects", "Workers"],
"product_group": "Developer platform",
"video": true
Expand Down
1 change: 0 additions & 1 deletion src/content/release-notes/api-deprecations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ productLink: "/fundamentals/"
productArea: Core platform
productAreaLink: /fundamentals/reference/changelog/platform/
entries:

- publish_date: "2025-03-20"
title: "Cloudflare Radar: Attack top industry and vertical endpoints"
description: |-
Expand Down
Loading