Skip to main content
ClaudeWave
Skill169 repo starsupdated 29d ago

cloudflare-email-routing

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.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/secondsky/claude-skills /tmp/cloudflare-email-routing && cp -r /tmp/cloudflare-email-routing/plugins/cloudflare-email-routing/skills/cloudflare-email-routing ~/.claude/skills/cloudflare-email-routing
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# 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 Prevent
access-control-rbacSkill

Role-based access control (RBAC) with permissions and policies. Use for admin dashboards, enterprise access, multi-tenant apps, fine-grained authorization, or encountering permission hierarchies, role inheritance, policy conflicts.

aceternity-uiSkill

100+ animated React components (Aceternity UI) for Next.js with Tailwind. Use for hero sections, parallax, 3D effects, or encountering animation, shadcn CLI integration errors.

ai-elements-chatbotSkill

shadcn/ui AI chat components for conversational interfaces. Use for streaming chat, tool/function displays, reasoning visualization, or encountering Next.js App Router setup, Tailwind v4 integration, AI SDK v5 migration errors.

ai-sdk-coreSkill

Vercel AI SDK v5 for backend AI (text generation, structured output, tools, agents). Multi-provider. Use for server-side AI or encountering AI_APICallError, AI_NoObjectGeneratedError, streaming failures.

ai-sdk-uiSkill

Vercel AI SDK v5 React hooks (useChat, useCompletion, useObject) for AI chat interfaces. Use for React/Next.js AI apps or encountering parse stream errors, no response, streaming issues.

api-authenticationSkill

Secure API authentication with JWT, OAuth 2.0, API keys. Use for authentication systems, third-party integrations, service-to-service communication, or encountering token management, security headers, auth flow errors.

api-changelog-versioningSkill

Creates comprehensive API changelogs documenting breaking changes, deprecations, and migration strategies for API consumers. Use when managing API versions, communicating breaking changes, or creating upgrade guides.

api-contract-testingSkill

Verifies API contracts between services using consumer-driven contracts, schema validation, and tools like Pact. Use when testing microservices communication, preventing breaking changes, or validating OpenAPI specifications.