Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Cloudflare Email Routing

ASecurity

Cloudflare Email Routing for receiving/sending emails via Workers. Use for email workers, forwarding, allowlists, or encountering Email Trigger errors, worker call failures, SPF issues.

222 stars
0 votes
0 copies
0 views
Added 2/7/2026
developmenttypescriptbashnodeawstestingdebuggingdatabasedocumentation

Security Analysis

A100/100

Pro scans all 12 files and shows the line behind each finding

Scanned 2/12/2026

$npx -y skills add secondsky/claude-skills --skill cloudflare-email-routing --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Cloudflare Email Routing?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Cloudflare Email Routing
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/secondsky-cloudflare-email-routing/badge)](https://www.skillsdirectory.com/skills/secondsky-cloudflare-email-routing)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: cloudflare-email-routing
description: Cloudflare Email Routing for receiving/sending emails via Workers. Use for email workers, forwarding, allowlists, or encountering Email Trigger errors, worker call failures, SPF issues.

  Keywords: Cloudflare Email Routing, Email Workers, send email, receive email, email forwarding, email allowlist, email blocklist, postal-mime, mimetext, cloudflare:email, EmailMessage, ForwardableEmailMessage, EmailEvent, MX records, SPF, DKIM, email worker binding, send_email binding, wrangler email, email handler, email routing worker, "Email Trigger not available", "failed to call worker", email delivery failed, email not forwarding, destination address not verified
license: MIT
metadata:
  version: "2.0.0"
  last_verified: "2025-11-18"
  production_tested: true
  token_savings: "~60%"
  errors_prevented: 8
  templates_included: 0
  references_included: 1
---

# Cloudflare Email Routing

**Status**: Production Ready ✅ | **Last Verified**: 2025-11-18

---

## What Is Email Routing?

Two capabilities:

1. **Email Workers** - Receive and process incoming emails (allowlists, forwarding, parsing)
2. **Send Email** - Send emails from Workers to verified addresses

Both **free** and work together for complete email functionality.

---

## Quick Start (10 Minutes)

### Part 1: Enable Email Routing

**Dashboard setup:**
1. Dashboard → Domain → **Email** → **Email Routing**
2. **Enable Email Routing** → **Add records and enable**
3. Create destination address:
   - Custom: `hello@yourdomain.com`
   - Destination: Your email
   - Verify via email
4. ✅ Basic forwarding active

### Part 2: Receiving Emails (Email Workers)

**Install dependencies:**

```bash
bun add postal-mime@2.5.0 mimetext@3.0.27
```

**Create email worker:**

```typescript
// src/email.ts
import { EmailMessage } from 'cloudflare:email';
import PostalMime from 'postal-mime';

export default {
  async email(message, env, ctx) {
    const parser = new PostalMime.default();
    const email = await parser.parse(await new Response(message.raw).arrayBuffer());

    console.log('From:', message.from);
    console.log('Subject:', email.subject);

    // Forward to destination
    await message.forward('you@gmail.com');
  }
};
```

**Configure wrangler.jsonc:**

```jsonc
{
  "name": "email-worker",
  "main": "src/email.ts",
  "compatibility_date": "2025-10-11",
  "node_compat": true  // Required!
}
```

**Deploy and connect:**

```bash
bunx wrangler deploy
```

Dashboard → Email Workers → **Create address** → Select worker

### Part 3: Sending Emails

**Add send email binding:**

```jsonc
{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-10-11",
  "send_email": [
    {
      "name": "SES",
      "destination_address": "user@example.com"
    }
  ]
}
```

**Send from worker:**

```typescript
import { EmailMessage } from 'cloudflare:email';
import { createMimeMessage } from 'mimetext';

const msg = createMimeMessage();
msg.setSender({ name: 'App', addr: 'noreply@yourdomain.com' });
msg.setRecipient('user@example.com');
msg.setSubject('Hello!');
msg.addMessage({
  contentType: 'text/plain',
  data: 'Email body here'
});

const message = new EmailMessage(
  'noreply@yourdomain.com',
  'user@example.com',
  msg.asRaw()
);

await env.SES.send(message);
```

**Load `references/setup-guide.md` for complete walkthrough.**

---

## Critical Rules

### Always Do ✅

1. **Enable node_compat: true** for postal-mime
2. **Verify destination addresses** before sending
3. **Parse with postal-mime** for email content
4. **Use mimetext** for creating emails
5. **Check message.from** for allowlists
6. **Forward with message.forward()** (not manual)
7. **Handle errors** (email delivery can fail)
8. **Test with real emails** (not just dashboard)
9. **Add MX records** (automatic via dashboard)
10. **Log email activity** for debugging

### Never Do ❌

1. **Never skip node_compat** (postal-mime requires it)
2. **Never send without verification** (delivery fails)
3. **Never hardcode email addresses** in public code
4. **Never skip parsing** (raw email is hard to work with)
5. **Never ignore spam** (implement allowlists/blocklists)
6. **Never exceed Gmail limits** (500 emails/day to Gmail)
7. **Never skip error handling** (emails can fail)
8. **Never modify DNS manually** (use dashboard)
9. **Never expose email content** in logs (PII)
10. **Never assume instant delivery** (email is async)

---

## Common Patterns

### Allowlist

```typescript
const allowlist = ['approved@domain.com'];

if (!allowlist.includes(message.from)) {
  message.setReject('Not on allowlist');
  return;
}

await message.forward('you@gmail.com');
```

### Blocklist

```typescript
const blocklist = ['spam@bad.com'];

if (blocklist.includes(message.from)) {
  message.setReject('Blocked');
  return;
}

await message.forward('you@gmail.com');
```

### Reply to Email

```typescript
const msg = createMimeMessage();
msg.setSender({ addr: 'noreply@yourdomain.com' });
msg.setRecipient(message.from);
msg.setSubject(`Re: ${email.subject}`);
msg.addMessage({
  contentType: 'text/plain',
  data: 'Thanks for your email!'
});

const reply = new EmailMessage(
  'noreply@yourdomain.com',
  message.from,
  msg.asRaw()
);

await env.SES.send(reply);
```

### Parse Attachments

```typescript
const parser = new PostalMime.default();
const email = await parser.parse(await new Response(message.raw).arrayBuffer());

for (const attachment of email.attachments) {
  console.log('Filename:', attachment.filename);
  console.log('Type:', attachment.mimeType);
  console.log('Size:', attachment.content.byteLength);
}
```

### Custom Routing Logic

```typescript
async email(message, env, ctx) {
  const parser = new PostalMime.default();
  const email = await parser.parse(await new Response(message.raw).arrayBuffer());

  // Route based on subject
  if (email.subject.includes('[Support]')) {
    await message.forward('support@yourdomain.com');
  } else if (email.subject.includes('[Sales]')) {
    await message.forward('sales@yourdomain.com');
  } else {
    await message.forward('general@yourdomain.com');
  }
}
```

---

## Email Message Properties

### Incoming Messages (ForwardableEmailMessage)

```typescript
message.from        // Sender email
message.to          // Recipient email
message.headers     // Email headers
message.raw         // Raw email stream
message.rawSize     // Size in bytes

// Methods
message.forward(address)        // Forward to address
message.setReject(reason)       // Reject email
```

### Parsed Email (PostalMime)

```typescript
email.from          // { name, address }
email.to            // [{ name, address }]
email.subject       // Subject line
email.text          // Plain text body
email.html          // HTML body
email.attachments   // Array of attachments
email.headers       // All headers
```

---

## Top 5 Errors Prevented

1. **"Email Trigger not available"**: Enable node_compat: true
2. **Destination not verified**: Verify all send destinations
3. **Gmail rate limit**: Max 500 emails/day to Gmail
4. **SPF permerror**: Use dashboard to configure DNS
5. **Worker call failed**: Check logs for parsing errors

---

## Use Cases

### Use Case 1: Support Ticket System

```typescript
async email(message, env, ctx) {
  const parser = new PostalMime.default();
  const email = await parser.parse(await new Response(message.raw).arrayBuffer());

  // Create ticket in database
  await env.DB.prepare(
    'INSERT INTO tickets (email, subject, body, created_at) VALUES (?, ?, ?, ?)'
  ).bind(message.from, email.subject, email.text, Date.now()).run();

  // Send confirmation
  const msg = createMimeMessage();
  msg.setSender({ addr: 'support@yourdomain.com' });
  msg.setRecipient(message.from);
  msg.setSubject('Ticket Created');
  msg.addMessage({
    contentType: 'text/plain',
    data: 'Your support ticket has been created.'
  });

  const confirmation = new EmailMessage(
    'support@yourdomain.com',
    message.from,
    msg.asRaw()
  );

  await env.SES.send(confirmation);
}
```

### Use Case 2: Email Notifications

```typescript
export default {
  async fetch(request, env, ctx) {
    // User signup
    const { email, name } = await request.json();

    const msg = createMimeMessage();
    msg.setSender({ name: 'App', addr: 'noreply@yourdomain.com' });
    msg.setRecipient(email);
    msg.setSubject('Welcome!');
    msg.addMessage({
      contentType: 'text/html',
      data: `<h1>Welcome, ${name}!</h1>`
    });

    const message = new EmailMessage(
      'noreply@yourdomain.com',
      email,
      msg.asRaw()
    );

    await env.SES.send(message);

    return new Response('Welcome email sent!');
  }
};
```

### Use Case 3: Email Forwarding with Filtering

```typescript
async email(message, env, ctx) {
  const parser = new PostalMime.default();
  const email = await parser.parse(await new Response(message.raw).arrayBuffer());

  // Filter spam keywords
  const spamKeywords = ['viagra', 'lottery', 'prince'];
  const isSpam = spamKeywords.some(keyword =>
    email.subject.toLowerCase().includes(keyword) ||
    email.text.toLowerCase().includes(keyword)
  );

  if (isSpam) {
    message.setReject('Spam detected');
    return;
  }

  await message.forward('you@gmail.com');
}
```

---

## When to Load References

### Load `references/setup-guide.md` when:
- First-time Email Routing setup
- Configuring MX records
- Setting up email workers
- Configuring send email binding
- Complete walkthrough needed

---

## Using Bundled Resources

**References** (`references/`):
- `setup-guide.md` - Complete setup walkthrough (enabling routing, email workers, send email)
- `common-errors.md` - All 8 documented errors with solutions and prevention
- `dns-setup.md` - MX records, SPF, DKIM configuration guide
- `local-development.md` - Local testing and development patterns

**Templates** (`templates/`):
- `receive-basic.ts` - Basic email receiving worker
- `receive-allowlist.ts` - Email allowlist implementation
- `receive-blocklist.ts` - Email blocklist implementation
- `receive-reply.ts` - Auto-reply email worker
- `send-basic.ts` - Basic send email example
- `send-notification.ts` - Notification email pattern
- `wrangler-email.jsonc` - Wrangler configuration for email routing

---

## Official Documentation

- **Email Routing**: https://developers.cloudflare.com/email-routing/
- **Email Workers**: https://developers.cloudflare.com/email-routing/email-workers/
- **Send Email**: https://developers.cloudflare.com/email-routing/email-workers/send-email-workers/

---

**Questions? Issues?**

1. Check `references/setup-guide.md` for complete setup
2. Verify node_compat: true in wrangler.jsonc
3. Confirm destination addresses verified
4. Check logs for errors

Attribution

secondskysecondsky
View sourceSee grades on GitHubMore from secondsky →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Clean Code

Pragmatic coding standards - concise, direct, no over-engineering, no unnecessary comments

304955 votes

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

285172 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2222 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

10341 votes
View all in development →