Cloudflare Email Workers: run code when an email arrives
By Flavio Copes
How to receive and process incoming email with Cloudflare Email Workers, parse it with postal-mime, and forward or act on it.
We usually think about sending email. But sometimes you want to receive it and do something. Turn a support email into a ticket, post it to Slack, or pull data out of an attachment.
Email Workers let you run code whenever an email arrives at an address you own. The email becomes the trigger, the same way an HTTP request triggers a normal Worker.
How it fits together
First you set up Email Routing on your domain in the Cloudflare dashboard. That’s what lets Cloudflare receive mail for you. Then you route an address to a Worker instead of forwarding it to an inbox.
Once that’s done, every email to that address calls your Worker’s email handler.
The email handler
A normal Worker has a fetch handler. An Email Worker has an email handler:
export default {
async email(message, env, ctx) {
console.log('From:', message.from)
console.log('To:', message.to)
console.log('Subject:', message.headers.get('subject'))
},
}
The message gives you the basics right away: from, to, and the headers. For the subject and other header fields, you read them from message.headers, just like a normal Headers object.
Read the body with postal-mime
The basics are easy, but the actual body of an email is messy. Real emails are MIME: multiple parts, text and HTML versions, different encodings, attachments. You don’t want to parse that by hand.
The standard tool is postal-mime. Install it:
npm install postal-mime
Then parse the raw message:
import PostalMime from 'postal-mime'
export default {
async email(message, env, ctx) {
const email = await PostalMime.parse(message.raw)
console.log('Subject:', email.subject)
console.log('Text:', email.text)
console.log('HTML:', email.html)
console.log('Attachments:', email.attachments)
},
}
message.raw is the full email. PostalMime.parse turns it into a clean object with subject, text, html, and attachments ready to use.
Forwarding
The simplest action is to forward the email somewhere:
export default {
async email(message, env, ctx) {
await message.forward('[email protected]')
},
}
The destination has to be a verified address in your Email Routing setup.
A real example: route by recipient
Here’s a pattern I like. One Worker handles several addresses and routes based on who the mail was sent to:
export default {
async email(message, env, ctx) {
if (message.to.includes('support@')) {
await message.forward('[email protected]')
} else if (message.to.includes('sales@')) {
await message.forward('[email protected]')
} else {
await message.forward('[email protected]')
}
},
}
Swap the forwards for whatever you need: write a row to D1, drop a job on a Queue, post to Slack.
Test it locally
You don’t need to send real email to test. Run wrangler dev, and it exposes a local endpoint you can POST a raw email to:
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 'From: [email protected]
To: [email protected]
Subject: Testing
Message-ID: <[email protected]>
Hello there'
That fires your email handler with the message, so you can build and debug without sending anything for real.
Where this shines
Email Workers turn your inbox into an API. Anything that arrives by email can kick off code: support automation, parsing receipts, handling replies, catching bounces.
Pair it with the rest of the platform, a Queue for the slow work, D1 to store results, and you’ve got a real email pipeline with no mail server to run. The full reference is in the Email Workers docs.
Related posts about cloudflare: