Skip to content

Commit 11ac5b1

Browse files
celsopenalosapedrosousa
authored
Email Workers local development (#21399)
* Email Workers local development * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Somhairle MacLeòid <[email protected]> * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Somhairle MacLeòid <[email protected]> * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Somhairle MacLeòid <[email protected]> * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Somhairle MacLeòid <[email protected]> * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Somhairle MacLeòid <[email protected]> * preview link no longer required * changelog entry * changes required * typo * closes code section * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Pedro Sousa <[email protected]> * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Pedro Sousa <[email protected]> * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Pedro Sousa <[email protected]> * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Pedro Sousa <[email protected]> * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Pedro Sousa <[email protected]> * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Pedro Sousa <[email protected]> * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Pedro Sousa <[email protected]> * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Pedro Sousa <[email protected]> * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Pedro Sousa <[email protected]> * Update src/content/docs/email-routing/email-workers/local-development.mdx Co-authored-by: Pedro Sousa <[email protected]> * update changelog --------- Co-authored-by: Celso Martinho <[email protected]> Co-authored-by: Somhairle MacLeòid <[email protected]> Co-authored-by: Pedro Sousa <[email protected]>
1 parent 00e2b03 commit 11ac5b1

File tree

8 files changed

+291
-10
lines changed

8 files changed

+291
-10
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
title: Local development support for Email Workers
3+
description: Developers can now use wrangler to do local development for Email Workers.
4+
date: 2025-04-08T15:00:00Z
5+
---
6+
7+
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`.
8+
9+
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):
10+
11+
```ts
12+
import * as PostalMime from 'postal-mime';
13+
14+
export default {
15+
async email(message, env, ctx) {
16+
const parser = new PostalMime.default();
17+
const rawEmail = new Response(message.raw);
18+
const email = await parser.parse(await rawEmail.arrayBuffer());
19+
console.log(email);
20+
},
21+
};
22+
23+
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:
24+
25+
```bash
26+
curl -X POST 'http://localhost:8787/cdn-cgi/handler/email' \
27+
--url-query '[email protected]' \
28+
--url-query '[email protected]' \
29+
--header 'Content-Type: application/json' \
30+
--data-raw 'Received: from smtp.example.com (127.0.0.1)
31+
by cloudflare-email.com (unknown) id 4fwwffRXOpyR
32+
for <[email protected]>; Tue, 27 Aug 2024 15:50:20 +0000
33+
From: "John" <[email protected]>
34+
35+
36+
Subject: Testing Email Workers Local Dev
37+
Content-Type: text/html; charset="windows-1252"
38+
X-Mailer: Curl
39+
Date: Tue, 27 Aug 2024 08:49:44 -0700
40+
Message-ID: <6114391943504294873000@ZSH-GHOSTTY>
41+
42+
Hi there'
43+
```
44+
45+
This is what you get in the console:
46+
47+
```json
48+
{
49+
headers: [
50+
{
51+
key: 'received',
52+
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'
53+
},
54+
{ key: 'from', value: '"John" <[email protected]>' },
55+
{ key: 'reply-to', value: '[email protected]' },
56+
{ key: 'to', value: '[email protected]' },
57+
{ key: 'subject', value: 'Testing Email Workers Local Dev' },
58+
{ key: 'content-type', value: 'text/html; charset="windows-1252"' },
59+
{ key: 'x-mailer', value: 'Curl' },
60+
{ key: 'date', value: 'Tue, 27 Aug 2024 08:49:44 -0700' },
61+
{
62+
key: 'message-id',
63+
value: '<6114391943504294873000@ZSH-GHOSTTY>'
64+
}
65+
],
66+
from: { address: '[email protected]', name: 'John' },
67+
to: [ { address: '[email protected]', name: '' } ],
68+
replyTo: [ { address: '[email protected]', name: '' } ],
69+
subject: 'Testing Email Workers Local Dev',
70+
messageId: '<6114391943504294873000@ZSH-GHOSTTY>',
71+
date: '2024-08-27T15:49:44.000Z',
72+
html: 'Hi there\n',
73+
attachments: []
74+
}
75+
```
76+
77+
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.

src/content/docs/email-routing/email-workers/demos.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
pcx_content_type: navigation
33
title: Demos
44
sidebar:
5-
order: 6
5+
order: 7
66

77
---
88

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

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

17-
<ExternalResources type="apps" products={["Email Workers"]} />
17+
<ExternalResources type="apps" products={["Email Workers"]} />
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
---
2+
title: Local Development
3+
sidebar:
4+
order: 6
5+
badge: Beta
6+
7+
---
8+
9+
import { Render, Type, MetaInfo, WranglerConfig } from "~/components";
10+
11+
You can test the behavior of an Email Worker script in local development using [wrangler dev](/workers/wrangler/commands/#dev).
12+
13+
This is the minimal wrangler configuration required to run an Email Worker locally:
14+
15+
<WranglerConfig>
16+
17+
```jsonc
18+
{
19+
"send_email": [
20+
{
21+
"name": "EMAIL"
22+
}
23+
]
24+
}
25+
```
26+
27+
</WranglerConfig>
28+
29+
:::note
30+
31+
If you want to deploy your script you need to [enable Email Routing](/email-routing/get-started/enable-email-routing/) and have at least one verified [destination address](/email-routing/setup/email-routing-addresses/#destination-addresses).
32+
33+
:::
34+
35+
You can now test receiving, replying, and sending emails in your local environment.
36+
37+
## Receive an email
38+
39+
Consider this example Email Worker script that uses the open source [`postal-mime`](https://www.npmjs.com/package/postal-mime) email parser:
40+
41+
```ts
42+
import * as PostalMime from 'postal-mime';
43+
44+
export default {
45+
async email(message, env, ctx) {
46+
const parser = new PostalMime.default();
47+
const rawEmail = new Response(message.raw);
48+
const email = await parser.parse(await rawEmail.arrayBuffer());
49+
console.log(email);
50+
},
51+
};
52+
```
53+
54+
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:
55+
56+
```bash
57+
curl --request POST 'http://localhost:8787/cdn-cgi/handler/email' \
58+
--url-query '[email protected]' \
59+
--url-query '[email protected]' \
60+
--header 'Content-Type: application/json' \
61+
--data-raw 'Received: from smtp.example.com (127.0.0.1)
62+
by cloudflare-email.com (unknown) id 4fwwffRXOpyR
63+
for <[email protected]>; Tue, 27 Aug 2024 15:50:20 +0000
64+
From: "John" <[email protected]>
65+
66+
67+
Subject: Testing Email Workers Local Dev
68+
Content-Type: text/html; charset="windows-1252"
69+
X-Mailer: Curl
70+
Date: Tue, 27 Aug 2024 08:49:44 -0700
71+
Message-ID: <6114391943504294873000@ZSH-GHOSTTY>
72+
73+
Hi there'
74+
```
75+
76+
This is what you get in the console:
77+
78+
```json
79+
{
80+
headers: [
81+
{
82+
key: 'received',
83+
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'
84+
},
85+
{ key: 'from', value: '"John" <[email protected]>' },
86+
{ key: 'reply-to', value: '[email protected]' },
87+
{ key: 'to', value: '[email protected]' },
88+
{ key: 'subject', value: 'Testing Email Workers Local Dev' },
89+
{ key: 'content-type', value: 'text/html; charset="windows-1252"' },
90+
{ key: 'x-mailer', value: 'Curl' },
91+
{ key: 'date', value: 'Tue, 27 Aug 2024 08:49:44 -0700' },
92+
{
93+
key: 'message-id',
94+
value: '<6114391943504294873000@ZSH-GHOSTTY>'
95+
}
96+
],
97+
from: { address: '[email protected]', name: 'John' },
98+
to: [ { address: '[email protected]', name: '' } ],
99+
replyTo: [ { address: '[email protected]', name: '' } ],
100+
subject: 'Testing Email Workers Local Dev',
101+
messageId: '<6114391943504294873000@ZSH-GHOSTTY>',
102+
date: '2024-08-27T15:49:44.000Z',
103+
html: 'Hi there\n',
104+
attachments: []
105+
}
106+
```
107+
108+
## Send an email
109+
110+
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:
111+
112+
```ts
113+
import { EmailMessage } from "cloudflare:email";
114+
import { createMimeMessage } from 'mimetext';
115+
116+
export default {
117+
async fetch(request, env, ctx) {
118+
const msg = createMimeMessage();
119+
msg.setSender({ name: 'Sending email test', addr: '[email protected]' });
120+
msg.setRecipient('[email protected]');
121+
msg.setSubject('An email generated in a worker');
122+
msg.addMessage({
123+
contentType: 'text/plain',
124+
data: `Congratulations, you just sent an email from a worker.`,
125+
});
126+
127+
var message = new EmailMessage('[email protected]', '[email protected]', msg.asRaw());
128+
await env.EMAIL.send(message);
129+
return Response.json({ ok: true });
130+
}
131+
};
132+
```
133+
134+
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:
135+
136+
```txt
137+
⎔ Starting local server...
138+
[wrangler:inf] Ready on http://localhost:8787
139+
[wrangler:inf] GET / 200 OK (19ms)
140+
[wrangler:inf] send_email binding called with the following message:
141+
/var/folders/33/pn86qymd0w50htvsjp93rys40000gn/T/miniflare-f9be031ff417b2e67f2ac4cf94cb1b40/files/email/33e0a255-a7df-4f40-b712-0291806ed2b3.eml
142+
```
143+
144+
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:
145+
146+
```
147+
Date: Fri, 04 Apr 2025 12:27:08 +0000
148+
From: =?utf-8?B?U2VuZGluZyBlbWFpbCB0ZXN0?= <[email protected]>
149+
150+
Message-ID: <[email protected]>
151+
Subject: =?utf-8?B?QW4gZW1haWwgZ2VuZXJhdGVkIGluIGEgd29ya2Vy?=
152+
MIME-Version: 1.0
153+
Content-Type: text/plain; charset=UTF-8
154+
Content-Transfer-Encoding: 7bit
155+
156+
Congratulations, you just sent an email from a worker.
157+
```
158+
159+
## Reply to and forward messages
160+
161+
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:
162+
163+
```ts
164+
import * as PostalMime from 'postal-mime';
165+
import { createMimeMessage } from 'mimetext';
166+
import { EmailMessage } from 'cloudflare:email';
167+
168+
export default {
169+
async email(message, env: any, ctx: any) {
170+
// parses incoming message
171+
const parser = new PostalMime.default();
172+
const rawEmail = new Response(message.raw);
173+
const email = await parser.parse(await rawEmail.arrayBuffer());
174+
175+
// creates some ticket
176+
// const ticket = await createTicket(email);
177+
178+
// creates reply message
179+
const msg = createMimeMessage();
180+
msg.setSender({ name: 'Thank you for your contact', addr: '[email protected]' });
181+
msg.setRecipient(message.from);
182+
msg.setHeader('In-Reply-To', message.headers.get('Message-ID'));
183+
msg.setSubject('An email generated in a worker');
184+
msg.addMessage({
185+
contentType: 'text/plain',
186+
data: `This is an automated reply. We received you email with the subject "${email.subject}", and will handle it as soon as possible.`,
187+
});
188+
189+
const replyMessage = new EmailMessage('[email protected]', message.from, msg.asRaw());
190+
191+
await message.reply(replyMessage);
192+
await message.forward("[email protected]");
193+
},
194+
};
195+
```
196+
197+
Run `npx wrangler dev` and use curl to `POST` the same message from the [Receive an email](#receive-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:
198+
199+
```txt
200+
⎔ Starting local server...
201+
[wrangler:inf] Ready on http://localhost:8787
202+
[wrangler:inf] Email handler replied to sender with the following message:
203+
/var/folders/33/pn86qymd0w50htvsjp93rys40000gn/T/miniflare-381a79d7efa4e991607b30a079f6b17d/files/email/a1db7ebb-ccb4-45ef-b315-df49c6d820c0.eml
204+
[wrangler:inf] Email handler forwarded message with
205+
206+
```

src/content/docs/email-routing/email-workers/runtime-api.mdx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ An `EmailEvent` is the event type to programmatically process your emails with a
1717
`EmailEvent` can be handled in Workers functions written using the Service Worker syntax by attaching to the `email` event with `addEventListener`:
1818

1919
```js
20-
addEventListener("email", (event) => {
21-
event.message.forward("<YOUR_EMAIL>");
20+
addEventListener("email", async (event) => {
21+
await event.message.forward("<YOUR_EMAIL>");
2222
});
2323
```
2424

@@ -41,7 +41,7 @@ addEventListener("email", (event) => {
4141
```js
4242
export default {
4343
async email(message, env, ctx) {
44-
message.forward("<YOUR_EMAIL>");
44+
await message.forward("<YOUR_EMAIL>");
4545
},
4646
};
4747
```
@@ -118,4 +118,3 @@ export default {
118118

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

src/content/glossary/fundamentals.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ entries:
112112
- term: protocol
113113
general_definition: |-
114114
a protocol is a set of rules governing the exchange or transmission of data between devices.
115-
115+
116116
- term: proxy server
117117
general_definition: |-
118118
the server that sits between the origin server and the client. Cloudflare is a proxy server for example.

src/content/learning-paths/china-network-overview.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"title": "Introduction to the China Network",
33
"path": "/learning-paths/china-network-overview/series/china-network-main-features-1/",
44
"priority": 1,
5-
"description":"Watch to learn how Cloudflare's China Network can help you improve performance, compliance, and connectivity for your users in mainland China.",
5+
"description": "Watch to learn how Cloudflare's China Network can help you improve performance, compliance, and connectivity for your users in mainland China.",
66
"products": ["China Network"],
77
"product_group": "Application performance",
88
"additional_groups": ["Application security"],

src/content/learning-paths/durable-objects-course.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"title": "Introduction to Durable Objects",
33
"path": "/learning-paths/durable-objects-course/series/introduction-to-series-1/",
44
"priority": 2,
5-
"description":"Dive into a hands-on Durable Objects project and learn how to build stateful apps using serverless architecture",
5+
"description": "Dive into a hands-on Durable Objects project and learn how to build stateful apps using serverless architecture",
66
"products": ["Durable Objects", "Workers"],
77
"product_group": "Developer platform",
88
"video": true

src/content/release-notes/api-deprecations.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ productLink: "/fundamentals/"
55
productArea: Core platform
66
productAreaLink: /fundamentals/reference/changelog/platform/
77
entries:
8-
98
- publish_date: "2025-03-20"
109
title: "Cloudflare Radar: Attack top industry and vertical endpoints"
1110
description: |-

0 commit comments

Comments
 (0)