Skip to content

Commit e0b5df5

Browse files
committed
Email Workers local development
1 parent 9f30453 commit e0b5df5

File tree

7 files changed

+222
-10
lines changed

7 files changed

+222
-10
lines changed

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: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
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 your `email()` handler in local development using [wrangler dev](/workers/wrangler/commands/#dev).
12+
13+
Right now local development for Email Workers is in beta. To use it, you need to install a preview release of Wrangler.
14+
15+
```bash
16+
npm install --save-dev https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/14106373814/npm-package-wrangler-8375
17+
```
18+
19+
Make sure you have Email Routing [enabled](/email-routing/get-started/enable-email-routing/), at least one verified [destination address](/email-routing/setup/email-routing-addresses/#destination-addresses), and your wrangler configuration declares the Email Workers binding:
20+
21+
<WranglerConfig>
22+
23+
```jsonc
24+
{
25+
"dev": {
26+
"port": 8787,
27+
"local_protocol": "http"
28+
},
29+
"send_email": [
30+
{
31+
"name": "EMAIL"
32+
}
33+
]
34+
}
35+
```
36+
37+
</WranglerConfig>
38+
39+
You can now test receiving, replying and sending emails in your local environment.
40+
41+
## Receiving an email
42+
43+
Consider this example Email Worker script that uses the opensource [postal-mime](https://www.npmjs.com/package/postal-mime) email parser:
44+
45+
```ts
46+
import * as PostalMime from 'postal-mime';
47+
48+
export default {
49+
50+
async email(message, env: any, ctx: any) {
51+
const parser = new PostalMime.default();
52+
const rawEmail = new Response(message.raw);
53+
const email = await parser.parse(await rawEmail.arrayBuffer());
54+
console.log(email);
55+
},
56+
57+
};
58+
```
59+
60+
Now when you run `npx wrangler dev`, wrangler will will expose a local `/cdn-cgi/handler/email` endpoint that you can `POST` email messages to and trigger your Worker script `email()` handler:
61+
62+
```bash
63+
curl -X POST 'http://localhost:8787/cdn-cgi/handler/email' \
64+
--url-query '[email protected]' \
65+
--url-query '[email protected]' \
66+
--header 'Content-Type: application/json' \
67+
--data-raw 'Received: from smtp.example.com (127.0.0.1)
68+
by cloudflare-email.com (unknown) id 4fwwffRXOpyR
69+
for <[email protected]>; Tue, 27 Aug 2024 15:50:20 +0000
70+
From: "John" <[email protected]>
71+
72+
73+
Subject: Testing Email Workers Local Dev
74+
Content-Type: text/html; charset="windows-1252"
75+
X-Mailer: Curl
76+
Date: Tue, 27 Aug 2024 08:49:44 -0700
77+
Message-ID: <6114391943504294873000@ZSH-GHOSTTY>
78+
79+
Hi there'
80+
```
81+
82+
This is what you get in the console:
83+
84+
```json
85+
{
86+
headers: [
87+
{
88+
key: 'received',
89+
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'
90+
},
91+
{ key: 'from', value: '"John" <[email protected]>' },
92+
{ key: 'reply-to', value: '[email protected]' },
93+
{ key: 'to', value: '[email protected]' },
94+
{ key: 'subject', value: 'Testing Email Workers Local Dev' },
95+
{ key: 'content-type', value: 'text/html; charset="windows-1252"' },
96+
{ key: 'x-mailer', value: 'Curl' },
97+
{ key: 'date', value: 'Tue, 27 Aug 2024 08:49:44 -0700' },
98+
{
99+
key: 'message-id',
100+
value: '<6114391943504294873000@ZSH-GHOSTTY>'
101+
}
102+
],
103+
from: { address: '[email protected]', name: 'John' },
104+
to: [ { address: '[email protected]', name: '' } ],
105+
replyTo: [ { address: '[email protected]', name: '' } ],
106+
subject: 'Testing Email Workers Local Dev',
107+
messageId: '<6114391943504294873000@ZSH-GHOSTTY>',
108+
date: '2024-08-27T15:49:44.000Z',
109+
html: 'Hi there\n',
110+
attachments: []
111+
}
112+
```
113+
114+
## Sending an email
115+
116+
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:
117+
118+
```ts
119+
import { EmailMessage } from "cloudflare:email";
120+
import { createMimeMessage } from 'mimetext';
121+
122+
export default {
123+
124+
async fetch(request, env, ctx) {
125+
const msg = createMimeMessage();
126+
msg.setSender({ name: 'Sending email test', addr: '[email protected]' });
127+
msg.setRecipient('[email protected]');
128+
msg.setSubject('An email generated in a worker');
129+
msg.addMessage({
130+
contentType: 'text/plain',
131+
data: `Congratulations, you just sent an email from a worker.`,
132+
});
133+
134+
var message = new EmailMessage('[email protected]', '[email protected]', msg.asRaw());
135+
await env.EMAIL.send(message);
136+
return Response.json({ ok: true });
137+
}
138+
139+
};
140+
```
141+
142+
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:
143+
144+
```bash
145+
[wrangler:inf] GET / 200 OK (19ms)
146+
send_email binding called with the following message:
147+
/var/folders/33/pn86qymd0w50htvsjp93rys40000gn/T/miniflare-25d0146a88267bfc8d4b91b42ad5a4d0/files/28318e5d-d1c8-4bf6-b2c1-9dcb40088391.eml
148+
```
149+
150+
Wrangler simulated `env.EMAIL.send()` by writing the email to a local file in [eml](https://datatracker.ietf.org/doc/html/rfc822). The file contains the raw email message:
151+
152+
```
153+
Date: Fri, 04 Apr 2025 12:27:08 +0000
154+
From: =?utf-8?B?U2VuZGluZyBlbWFpbCB0ZXN0?= <[email protected]>
155+
156+
Message-ID: <[email protected]>
157+
Subject: =?utf-8?B?QW4gZW1haWwgZ2VuZXJhdGVkIGluIGEgd29ya2Vy?=
158+
MIME-Version: 1.0
159+
Content-Type: text/plain; charset=UTF-8
160+
Content-Transfer-Encoding: 7bit
161+
162+
Congratulations, you just sent an email from a worker.
163+
```
164+
165+
## Replying to and forwarding messages
166+
167+
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:
168+
169+
```ts
170+
import * as PostalMime from 'postal-mime';
171+
import { createMimeMessage } from 'mimetext';
172+
import { EmailMessage } from 'cloudflare:email';
173+
174+
export default {
175+
async email(message, env: any, ctx: any) {
176+
// parses incoming message
177+
const parser = new PostalMime.default();
178+
const rawEmail = new Response(message.raw);
179+
const email = await parser.parse(await rawEmail.arrayBuffer());
180+
181+
// creates some ticket
182+
// const ticket = await createTicket(email);
183+
184+
// creates reply message
185+
const msg = createMimeMessage();
186+
msg.setSender({ name: 'Thank you for your contact', addr: '[email protected]' });
187+
msg.setRecipient(message.from);
188+
msg.setHeader('In-Reply-To', message.headers.get('Message-ID'));
189+
msg.setSubject('An email generated in a worker');
190+
msg.addMessage({
191+
contentType: 'text/plain',
192+
data: `This is an automated reply. We received you email with the subject "${email.subject}", and will handle it as soon as possible.`,
193+
});
194+
195+
var replyMessage = new EmailMessage('[email protected]', message.from, msg.asRaw());
196+
197+
await message.reply(replyMessage);
198+
await message.forward("[email protected]");
199+
},
200+
};
201+
```
202+
203+
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:
204+
205+
```bash
206+
[wrangler:inf] Ready on http://localhost:8787
207+
⎔ Starting local server...
208+
.reply() called from Email Handler with the following message:
209+
/var/folders/33/pn86qymd0w50htvsjp93rys40000gn/T/miniflare-9deb89ed3538650182911d0c23676206/files/935d333b-3850-4d3b-b042-4d3b9b53469c.eml
210+
.forward() called from Email Handler with
211+
212+
```
213+
214+
Note that Email Workers local development is still in beta and requires a preview version of wrangler. This page will update when we merge it into the main branch.

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)