-
Notifications
You must be signed in to change notification settings - Fork 10.2k
Email Workers local development #21399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
e0b5df5
Email Workers local development
celso 40d3dd8
Update src/content/docs/email-routing/email-workers/local-development…
celso ce3c1a2
Update src/content/docs/email-routing/email-workers/local-development…
celso 2347699
Update src/content/docs/email-routing/email-workers/local-development…
celso 160128a
Update src/content/docs/email-routing/email-workers/local-development…
celso f3184c6
Update src/content/docs/email-routing/email-workers/local-development…
celso 1235570
preview link no longer required
celso 8441c2d
changelog entry
celso 114241b
changes required
celso 9a154dd
typo
celso 966a3ba
closes code section
celso fbc3ab6
Update src/content/docs/email-routing/email-workers/local-development…
celso 154ebf1
Update src/content/docs/email-routing/email-workers/local-development…
celso 7409620
Update src/content/docs/email-routing/email-workers/local-development…
celso a238063
Update src/content/docs/email-routing/email-workers/local-development…
celso c0f84a4
Update src/content/docs/email-routing/email-workers/local-development…
celso 3538283
Update src/content/docs/email-routing/email-workers/local-development…
celso 75de507
Update src/content/docs/email-routing/email-workers/local-development…
celso 32662f3
Update src/content/docs/email-routing/email-workers/local-development…
celso eb91077
Update src/content/docs/email-routing/email-workers/local-development…
celso 14621f6
Update src/content/docs/email-routing/email-workers/local-development…
celso c3bc5cc
update changelog
celso 3fb0a90
Merge branch 'celso/email-localdev' of github.com:cloudflare/cloudfla…
celso File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
214 changes: 214 additions & 0 deletions
214
src/content/docs/email-routing/email-workers/local-development.mdx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,214 @@ | ||||||
| --- | ||||||
| title: Local Development | ||||||
| sidebar: | ||||||
| order: 6 | ||||||
| badge: Beta | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| import { Render, Type, MetaInfo, WranglerConfig } from "~/components"; | ||||||
|
|
||||||
| You can test the behavior of your `email()` handler in local development using [wrangler dev](/workers/wrangler/commands/#dev). | ||||||
|
|
||||||
| Right now local development for Email Workers is in beta. To use it, you need to install a preview release of Wrangler. | ||||||
|
|
||||||
| ```bash | ||||||
| npm install --save-dev https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/14106373814/npm-package-wrangler-8375 | ||||||
| ``` | ||||||
|
|
||||||
| 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: | ||||||
|
|
||||||
| <WranglerConfig> | ||||||
|
|
||||||
| ```jsonc | ||||||
| { | ||||||
| "dev": { | ||||||
| "port": 8787, | ||||||
| "local_protocol": "http" | ||||||
| }, | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| "send_email": [ | ||||||
| { | ||||||
| "name": "EMAIL" | ||||||
| } | ||||||
| ] | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| </WranglerConfig> | ||||||
|
|
||||||
| You can now test receiving, replying and sending emails in your local environment. | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| ## Receiving an email | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| Consider this example Email Worker script that uses the opensource [postal-mime](https://www.npmjs.com/package/postal-mime) email parser: | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| ```ts | ||||||
| import * as PostalMime from 'postal-mime'; | ||||||
|
|
||||||
| export default { | ||||||
|
|
||||||
| async email(message, env: any, ctx: any) { | ||||||
| const parser = new PostalMime.default(); | ||||||
| const rawEmail = new Response(message.raw); | ||||||
| const email = await parser.parse(await rawEmail.arrayBuffer()); | ||||||
| console.log(email); | ||||||
| }, | ||||||
|
|
||||||
| }; | ||||||
| ``` | ||||||
celso marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
| 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: | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| ```bash | ||||||
| curl -X POST 'http://localhost:8787/cdn-cgi/handler/email' \ | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| --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 | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| 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: | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| ```ts | ||||||
| import { EmailMessage } from "cloudflare:email"; | ||||||
| import { createMimeMessage } from 'mimetext'; | ||||||
|
|
||||||
| export default { | ||||||
|
|
||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| 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 }); | ||||||
| } | ||||||
|
|
||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| }; | ||||||
| ``` | ||||||
|
|
||||||
| 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: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Let's avoid "see" here. |
||||||
|
|
||||||
| ```bash | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| [wrangler:inf] GET / 200 OK (19ms) | ||||||
| send_email binding called with the following message: | ||||||
| /var/folders/33/pn86qymd0w50htvsjp93rys40000gn/T/miniflare-25d0146a88267bfc8d4b91b42ad5a4d0/files/28318e5d-d1c8-4bf6-b2c1-9dcb40088391.eml | ||||||
| ``` | ||||||
|
|
||||||
| 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: | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| ``` | ||||||
| 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 | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| 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.`, | ||||||
| }); | ||||||
|
|
||||||
| var replyMessage = new EmailMessage('[email protected]', message.from, msg.asRaw()); | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| 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: | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| ```bash | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| [wrangler:inf] Ready on http://localhost:8787 | ||||||
| ⎔ Starting local server... | ||||||
| .reply() called from Email Handler with the following message: | ||||||
| /var/folders/33/pn86qymd0w50htvsjp93rys40000gn/T/miniflare-9deb89ed3538650182911d0c23676206/files/935d333b-3850-4d3b-b042-4d3b9b53469c.eml | ||||||
| .forward() called from Email Handler with | ||||||
| rcptTo: [email protected] | ||||||
celso marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| ``` | ||||||
|
|
||||||
| 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. | ||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.