Cloudflare Workers DMARC Reporting Integration
DMARC aggregate reports are invaluable for understanding your email ecosystem. They provide a high-level overview of who is sending email on behalf of your domain, what authentication methods they're using (SPF, DKIM), and how DMARC policies are being applied. However, receiving these reports can quickly become a logistical challenge. They arrive as XML attachments, often gzipped, via email, and the volume can be substantial, especially for domains with high email traffic.
Manually collecting and parsing these reports is impractical. This is where automation comes in. Cloudflare Workers offer a lightweight, cost-effective, and globally distributed solution to automatically receive, process, and forward DMARC aggregate reports to a specialized parser like Aligned. This article will guide you through setting up a Cloudflare Worker to handle your DMARC reporting integration, ensuring you get actionable insights without the manual overhead.
Why Cloudflare Workers for DMARC?
Cloudflare Workers provide a serverless execution environment that's ideal for tasks like this. Here's why they're a great fit for DMARC reporting:
- Serverless and Scalable: You don't manage any servers. Cloudflare handles scaling automatically, effortlessly accommodating fluctuations in report volume.
- Cost-Effective: Workers are incredibly economical, with a generous free tier that often covers the needs of many small to medium-sized domains. Even at scale, the cost per execution is minimal.
- Global Distribution: Workers run on Cloudflare's edge network, close to your users and, in this case, close to where the reports might originate. This means low latency and high reliability.
- Email Routing Integration: Cloudflare's Email Routing service can directly forward incoming emails to Workers, providing a seamless pipeline for DMARC reports.
- Simple Development: Workers use standard JavaScript/TypeScript, making development straightforward for engineers familiar with web technologies.
DMARC Reporting Basics: The rua Tag
Before diving into Workers, let's quickly recap how DMARC aggregate reports work. Your DMARC record, published as a TXT record in your DNS, contains various tags that define your policy. The rua tag is key for reporting:
v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com; fo=1; pct=100;
The rua tag specifies an email address (or multiple, comma-separated) where aggregate reports should be sent. When a mail receiver (like Gmail, Outlook, etc.) processes email from your domain and applies your DMARC policy, it periodically generates an XML report summarizing the results. This report is then sent as an email attachment to the address(es) specified in your rua tag. These attachments are almost always gzipped (.gz) XML files.
The challenge is that dmarc@yourdomain.com needs to be an active, monitored mailbox capable of processing these incoming reports. This is precisely what our Cloudflare Worker will automate.
Setting Up Cloudflare Workers for DMARC Integration
The process involves three main steps: configuring Cloudflare Email Routing to receive reports, creating a Worker, and writing the Worker code to parse and forward.
Step 1: Configure Cloudflare Email Routing
First, you need a way to receive emails at the address specified in your rua record. Cloudflare Email Routing allows you to create custom email addresses for your domain and define rules for handling incoming mail.
- Enable Email Routing: In your Cloudflare dashboard, navigate to your domain, then go to "Email" > "Email Routing" and click "Enable Email Routing." Cloudflare will add the necessary MX records to your DNS.
- Create a Custom Address: Under "Custom Addresses," create an address like
dmarc@yourdomain.com. -
Create a Rule to Forward to Your Worker: You'll set up a rule that says: "If an email is sent to
dmarc@yourdomain.com, forward it to a Worker."Example: Cloudflare Email Routing Rule Setup
You'd configure a rule similar to this via the Cloudflare dashboard UI:
- Custom Address:
dmarc@yourdomain.com - Action: "Send to Worker"
- Worker: Select the Worker you're about to create (e.g.,
dmarc-report-forwarder).
This ensures that any email sent to your DMARC reporting address will be piped directly into your Cloudflare Worker for processing.
- Custom Address:
Step 2: Create Your Cloudflare Worker
In your Cloudflare dashboard, navigate to "Workers & Pages" and create a new application. Choose the "HTTP handler" template. Give your Worker a name, e.g., dmarc-report-forwarder.
Step 3: Write the Worker Code
This is where the magic happens. Your Worker will receive the incoming email, extract the DMARC XML, decompress it, and then forward it to Aligned's ingestion endpoint.
We'll use the mailchannels API, which Cloudflare Email Routing uses to send emails to Workers. The incoming Request object will contain the email data.
```javascript // Worker global constant for Aligned API key // For production, use Workers Secrets or KV for better security. const ALIGNED_API_KEY = "YOUR_ALIGNED_API_KEY"; // Replace with your actual Aligned API key const ALIGNED_INGESTION_URL = "https://ingest.dmarc.91-99-176-101.nip.io/v1/dmarc-report"; // Aligned's DMARC ingestion endpoint
/* * Handles incoming email requests from Cloudflare Email Routing. * @param {Request} request The incoming request object from Email Routing. / async function handleEmail(request) { // Ensure the request method is POST, as Email Routing sends POST requests. if (request.method !== "POST") { return new Response("Method Not Allowed", { status: 405 }); }
try { // Parse the incoming email data. Email Routing uses the Mailchannels API. // The email content is typically found in a 'email' form field. const formData = await request.formData(); const email = formData.get("email");
if (!email) {
console.error("No email field found in formData.");
return new Response("Bad Request: No email data", { status: 400 });
}
// Use a simple email parser (or a more robust one if needed)
// For Mailchannels, the 'email' field often contains the raw email content.
// We need to parse multipart/mixed to find attachments.
// A more complete solution might involve a library or more complex regex.
// For simplicity, we'll look for common DMARC attachment patterns.
const emailText = await new Response(email).text();
const boundaryMatch = emailText.match(/Content-Type: multipart\/mixed;\s*boundary="([^"]+)"/i);
if (!boundaryMatch) {
console.warn("Could not find multipart boundary in email. Skipping.");
// Could be a non-DMARC report or malformed email.
return new Response("No DMARC report found or malformed email", { status: 200 });
}
const boundary = boundaryMatch[1];
const parts = emailText.split(`--${boundary}`);
let dmarcReportXml = null;
for (const part of parts) {
// Look for gzipped XML attachments
const isGzipAttachment = part.includes("Content-Type: application/gzip") && part.includes("Content-Disposition: attachment");
const isXmlAttachment = part.includes("Content-Type: application/xml") && part.includes("Content-Disposition: attachment");
if (isGzipAttachment || isXmlAttachment) {
// Extract content transfer encoding (e.g., base64)
const encodingMatch = part.match(/Content-Transfer-Encoding: ([^\r\n]+)/i);
const encoding = encodingMatch ? encodingMatch[1].trim().toLowerCase() : '7bit'; // Default to 7bit
// Extract the raw content (after headers)
const contentStart = part.indexOf('\r\n\r\n') + 4;
let content = part.substring(contentStart).trim();
if (encoding === 'base64') {
content = atob(content.replace(/\s/g, '')); // Remove all whitespace before decoding
}
// Convert content to a Uint8Array for decompression
const contentBytes = new Text