Cloudflare
Email Workers
MIME
Automação
Serverless

Email Routing + Workers: Process emails programmatically at the edge

Routing email to a Worker instead of a destination address completely changes what you can do with inbound email.

Email Routing + Workers: Process emails programmatically at the edge

Most Cloudflare Email Routing tutorials show how to forward contato@seudominio.com to a Gmail. This solves the simplest use case — but hides the most interesting part of the service. When you route an email to a Worker instead of a destination address, the email becomes data: you read the sender, the subject, the headers, the full body, and decide what to do with it all inside a JavaScript function running at the edge. This significantly changes what is viable to build without your own email infrastructure.

The email handler and what you receive

The basic structure of an Email Worker uses an export named email within the default object:

export default { async email(message, env, ctx) { // message.from — endereço do remetente // message.to — endereço de destino no seu domínio // message.headers — objeto Headers com todos os cabeçalhos RFC 2822 // message.raw — ReadableStream com a mensagem completa } }

message.from and message.to are strings with addresses. message.headers is a standard Web API Headers object — you access message.headers.get('subject') or message.headers.get('x-mailer') the same way you would in a fetch handler. message.raw is a ReadableStream with the entire RFC 2822 message, including headers and body, with support for messages up to 25MB.

There are four available actions: message.forward(address) to forward to a verified address, message.reply(response) to reply, message.setReject(reason) to reject the message with an error message, or simply return without calling anything — which silently discards the message. You can combine: filter by sender, forward some, reject others, and process the rest.

The MIME parsing gap

Here's the point the documentation discreetly mentions: there is no built-in MIME parser. message.raw gives you the raw stream. If you want to extract the subject with UTF-8 encoding, the body in plain text, the alternative HTML, or the attachments, you need to parse the MIME yourself — or use a library.

postal-mime works well in the Workers environment. The default is to consume the stream, convert it to ArrayBuffer, and pass it to the parser:

import PostalMime from 'postal-mime'; const raw = await new Response(message.raw).arrayBuffer(); const parsed = await new PostalMime().parse(raw); // parsed.subject, parsed.text, parsed.html, parsed.attachments

parsed.attachments is an array of objects with filename, mimeType, and content (ArrayBuffer). You can save the content in an R2 bucket, extract metadata from an NF-e in XML, or pass a PDF to an extraction API. The Workers runtime supports this without any additional configuration other than importing the library.

The cost of consuming the entire stream is memory. For messages with large attachments close to the 25MB limit, you are loading everything into memory in the Worker. In most cases this is not a problem, but in high volume pipelines it is worth monitoring.

Patterns that make sense to build here

Automatic creation of support tickets. The email arrives, you extract the sender, subject and body, assemble a payload and POST it to the Linear API, Zendesk, Notion, or any tool your team uses. The ticket now appears with the full context without anyone needing to copy and paste. For teams that receive email requests but work in issue tracking tools, this eliminates a constant manual step.

Capture of invoices and tax documents. You create a dedicated address — nfe@seudominio.com — and any supplier who sends NF-e by email has the XML or PDF attachment automatically processed: metadata extracted, file saved in R2, record created in D1. Worker does what a human would do, but without having to open the email.

Filtering before forwarding. With catch-all active, *@seudominio.com captures spam sent to random addresses on your domain. A Worker can check the sender against a list of known problematic domains stored in the KV, check if the subject contains typical spam patterns, and call message.setReject('spam detectado') before forwarding to your inbox. It's not a complete spam filter, but it reduces noise at no additional cost.

Reformatted alerts and notifications. Monitoring tools — Grafana, PagerDuty, CI tools — send email alerts in formats that aren't always readable on a phone. A Worker intercepts these emails, extracts the relevant information from the body, and posts a formatted message to a Slack or Discord channel via webhook. The original email can be discarded or forwarded as an archive.

What doesn't work as you think

message.reply() exists and works, but the answer comes from noreply@cloudflare.com. If you want the recipient to receive an automatic response that appears to come from suporte@seudominio.com, the Worker needs to call an outbound SMTP service — Resend, Mailgun, SES — passing the necessary headers. Email Routing does not have access to your domain's outbound stream.

If the Worker throws an uncaught exception, the email is rejected with error 500. There is no automatic retry, there is no dead-letter queue. Any logic that might fail needs try/catch with explicit fallback — typically a message.forward() to a manual triage address when something goes wrong in processing. Discovering this behavior in production, with rejected customer email, is an unpleasant experience.

Where does it make sense to go beyond forwarding

If Email Routing for you is just an alias for Gmail, you will never touch Workers and you don't need to. But if your team already uses Workers for other purposes — APIs, cron jobs, integrations — Email Worker fits into the same infrastructure with the same KV, D1, R2 bindings and external services. You are not adding a new piece to the stack; you are adding a trigger type to an environment that already exists.

The right time to stop simple forwarding is when you notice that someone on the team opens emails to copy information to another system more than once or twice a week. If the pattern is repeatable and the data is in the email, the Worker solves it in less time than the manual task will consume in a month.

Also read