← Files CloudflareARCHIVED FILE

skills/cloudflare/references/email-routing/patterns.md

5.28 KB · Sep 30, 2026 · 23:18 UTC

↓ Download file

# Common Patterns

## 1. Allowlist/Blocklist

```typescript
// Allowlist
const allowed = ["user@example.com", "trusted@corp.com"];
if (!allowed.includes(message.from)) {
  message.setReject("Not allowed");
  return;
}
await message.forward("inbox@corp.com");
```

## 2. Parse Email Body

```typescript
import PostalMime from 'postal-mime';

export default {
  async email(message, env, ctx) {
    // CRITICAL: Consume stream immediately
    const raw = await message.raw.arrayBuffer();

    const parser = new PostalMime();
    const email = await parser.parse(raw);

    console.log({
      subject: email.subject,
      text: email.text,
      html: email.html,
      from: email.from.address,
      attachments: email.attachments.length
    });

    await message.forward("inbox@corp.com");
  }
} satisfies ExportedHandler;
```

## 3. Spam Filter

```typescript
const score = parseFloat(message.headers.get("x-cf-spamh-score") || "0");
if (score > 5) {
  message.setReject("Spam detected");
  return;
}
await message.forward("inbox@corp.com");
```

## 4. Archive to R2

```typescript
interface Env { R2: R2Bucket; }

export default {
  async email(message, env, ctx) {
    const raw = await message.raw.arrayBuffer();

    const key = `${new Date().toISOString()}-${message.from}.eml`;
    await env.R2.put(key, raw, {
      httpMetadata: { contentType: "message/rfc822" }
    });

    await message.forward("inbox@corp.com");
  }
} satisfies ExportedHandler<Env>;
```

## 5. Store Metadata in KV

```typescript
import PostalMime from 'postal-mime';

interface Env { KV: KVNamespace; }

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

    const metadata = {
      from: email.from.address,
      subject: email.subject,
      timestamp: new Date().toISOString(),
      size: raw.byteLength
    };

    await env.KV.put(`email:${Date.now()}`, JSON.stringify(metadata));
    await message.forward("inbox@corp.com");
  }
} satisfies ExportedHandler<Env>;
```

## 6. Subject-Based Routing

```typescript
export default {
  async email(message, env, ctx) {
    const subject = message.headers.get("subject")?.toLowerCase() || "";

    if (subject.includes("[urgent]")) {
      await message.forward("oncall@corp.com");
    } else if (subject.includes("[billing]")) {
      await message.forward("billing@corp.com");
    } else if (subject.includes("[support]")) {
      await message.forward("support@corp.com");
    } else {
      await message.forward("general@corp.com");
    }
  }
} satisfies ExportedHandler;
```

## 7. Auto-Reply

```typescript
interface Env {
  EMAIL: SendEmail;
  REPLIED: KVNamespace;
}

export default {
  async email(message, env, ctx) {
    const msgId = message.headers.get("message-id");

    if (msgId && await env.REPLIED.get(msgId)) {
      await message.forward("archive@corp.com");
      return;
    }

    ctx.waitUntil((async () => {
      await env.EMAIL.send({
        from: "noreply@yourdomain.com",
        to: message.from,
        subject: "Re: " + (message.headers.get("subject") || ""),
        text: "Thank you. We'll respond within 24h."
      });
      if (msgId) await env.REPLIED.put(msgId, "1", { expirationTtl: 604800 });
    })());

    await message.forward("support@corp.com");
  }
} satisfies ExportedHandler<Env>;
```

## 8. Extract Attachments

```typescript
import PostalMime from 'postal-mime';

interface Env { ATTACHMENTS: R2Bucket; }

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

    for (const att of email.attachments) {
      const key = `${Date.now()}-${att.filename}`;
      await env.ATTACHMENTS.put(key, att.content, {
        httpMetadata: { contentType: att.mimeType }
      });
    }

    await message.forward("inbox@corp.com");
  }
} satisfies ExportedHandler<Env>;
```

## 9. Log to D1

```typescript
import PostalMime from 'postal-mime';

interface Env { DB: D1Database; }

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

    ctx.waitUntil(
      env.DB.prepare("INSERT INTO log (ts, from_addr, subj) VALUES (?, ?, ?)")
        .bind(new Date().toISOString(), email.from.address, email.subject || "")
        .run()
    );

    await message.forward("inbox@corp.com");
  }
} satisfies ExportedHandler<Env>;
```

## 10. Multi-Tenant

```typescript
interface Env { TENANTS: KVNamespace; }

export default {
  async email(message, env, ctx) {
    const subdomain = message.to.split("@")[1].split(".")[0];
    const config = await env.TENANTS.get(subdomain, "json") as { forward: string } | null;

    if (!config) {
      message.setReject("Unknown tenant");
      return;
    }

    await message.forward(config.forward);
  }
} satisfies ExportedHandler<Env>;
```

## Summary

| Pattern | Use Case | Storage |
|---------|----------|---------|
| Allowlist | Security | None |
| Parse | Body/attachments | None |
| Spam Filter | Reduce spam | None |
| R2 Archive | Email storage | R2 |
| KV Meta | Analytics | KV |
| Subject Route | Dept routing | None |
| Auto-Reply | Support | KV |
| Attachments | Doc mgmt | R2 |
| D1 Log | Audit trail | D1 |
| Multi-Tenant | SaaS | KV |

SHA-256: 150f206ecaa8fa671829dbd0822f579e48c550c1b147d21312844904cc7f715e