Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cloudflare/agents/llms.txt

Use this file to discover all available pages before exploring further.

An email-processing agent using Cloudflare Email Routing. Demonstrates email parsing, auto-reply with HMAC-signed headers for secure routing, loop prevention, and comprehensive security testing.

What it demonstrates

  • Email Routing - Routes emails to agents based on email addresses (e.g., agent+id@domain.com)
  • Secure Reply Routing - HMAC-signed headers for secure reply flows
  • Email Parsing - Uses PostalMime to parse incoming emails
  • Auto-Reply - Automatically responds to incoming emails with loop prevention
  • State Management - Tracks email count and stores recent emails
  • Security Tests - Comprehensive test suite including attack bypass attempts

Server Implementation

src/index.ts
import { Agent, routeAgentEmail, routeAgentRequest } from "agents";
import {
  createAddressBasedEmailResolver,
  createSecureReplyEmailResolver,
  type AgentEmail
} from "agents/email";
import PostalMime from "postal-mime";

interface EmailData {
  from: string;
  subject: string;
  text?: string;
  html?: string;
  to: string;
  timestamp: Date;
  messageId?: string;
}

interface EmailAgentState {
  emailCount: number;
  lastUpdated: Date;
  emails: EmailData[];
  autoReplyEnabled: boolean;
}

export class EmailAgent extends Agent<Env, EmailAgentState> {
  initialState = {
    autoReplyEnabled: true,
    emailCount: 0,
    emails: [],
    lastUpdated: new Date()
  };

  async onEmail(email: AgentEmail) {
    console.log("📧 Received email from:", email.from, "to:", email.to);

    const raw = await email.getRaw();
    const parsed = await PostalMime.parse(raw);

    const emailData: EmailData = {
      from: parsed.from?.address || email.from,
      html: parsed.html,
      messageId: parsed.messageId,
      subject: parsed.subject || "No Subject",
      text: parsed.text,
      timestamp: new Date(),
      to: email.to
    };

    const newState = {
      autoReplyEnabled: this.state.autoReplyEnabled,
      emailCount: this.state.emailCount + 1,
      emails: [...this.state.emails.slice(-9), emailData],
      lastUpdated: new Date()
    };

    this.setState(newState);

    if (newState.autoReplyEnabled && !this.isAutoReply(parsed)) {
      await this.replyToEmail(email, {
        fromName: "My Email Agent",
        body: `Thank you for your email!

I received your message with subject: "${email.headers.get("subject")}"

Current stats:
- Total emails processed: ${newState.emailCount}
- Last updated: ${newState.lastUpdated.toISOString()}

Best regards,
Email Agent`,
        secret: this.env.EMAIL_SECRET
      });
    }
  }

  private isAutoReply(
    parsed: Awaited<ReturnType<typeof PostalMime.parse>>
  ): boolean {
    // Check headers for auto-reply indicators
    for (const h of parsed.headers) {
      const header = h as Record<string, string | undefined>;

      // auto-submitted header (RFC 3834)
      const autoSubmitted = header["auto-submitted"];
      if (autoSubmitted && autoSubmitted.toLowerCase() !== "no") {
        return true;
      }

      // x-auto-response-suppress header (Microsoft)
      if (header["x-auto-response-suppress"]) {
        return true;
      }

      // precedence header
      const precedence = header.precedence;
      if (
        precedence &&
        ["bulk", "junk", "list", "auto_reply"].includes(
          precedence.toLowerCase()
        )
      ) {
        return true;
      }
    }

    // Check subject line for common auto-reply patterns
    const subject = (parsed.subject || "").toLowerCase();
    return (
      subject.includes("auto-reply") ||
      subject.includes("out of office") ||
      subject.includes("automatic reply")
    );
  }
}

export default {
  async email(email, env: Env) {
    console.log("📮 Email received via email handler");

    const secureReplyResolver = createSecureReplyEmailResolver(
      env.EMAIL_SECRET
    );
    const addressResolver = createAddressBasedEmailResolver("EmailAgent");

    await routeAgentEmail(email, env, {
      resolver: async (email, env) => {
        // Check if this is a reply to one of our outbound emails
        const replyRouting = await secureReplyResolver(email, env);
        if (replyRouting) return replyRouting;
        // Otherwise route based on recipient address
        return addressResolver(email, env);
      }
    });
  },
  async fetch(request: Request, env: Env) {
    return (
      (await routeAgentRequest(request, env)) ||
      new Response("Not found", { status: 404 })
    );
  }
} satisfies ExportedHandler<Env>;

Email Routing Strategies

Uses HMAC-signed headers to securely route email replies:
const secureResolver = createSecureReplyEmailResolver(env.EMAIL_SECRET, {
  maxAge: 7 * 24 * 60 * 60, // 7 days
  onInvalidSignature: (email, reason) => {
    console.warn(`Invalid signature from ${email.from}: ${reason}`);
  }
});
Security features:
  • HMAC-SHA256 signatures prevent header forgery
  • Timestamp validation prevents replay attacks
  • Constant-time comparison prevents timing attacks

2. Address-Based Routing

Routes based on email address patterns:
const resolver = createAddressBasedEmailResolver("EmailAgent");

// EmailAgent+user123@domain.com → { agentName: "EmailAgent", agentId: "user123" }
// john.doe@domain.com → { agentName: "EmailAgent", agentId: "john.doe" }
Routing rules:
  • With sub-addresses: localpart+subaddress@domain.comagentName: "localpart", agentId: "subaddress"
  • Without sub-addresses: localpart@domain.comagentName: defaultAgentName, agentId: "localpart"

3. Catch-All Routing

Routes all emails to a single agent:
const resolver = createCatchAllEmailResolver("EmailAgent", "main");
// All emails route to EmailAgent:main

Composing Resolvers

await routeAgentEmail(email, env, {
  resolver: async (email, env) => {
    // Try secure reply routing first (for replies)
    const replyRouting = await secureReplyResolver(email, env);
    if (replyRouting) return replyRouting;

    // Fall back to address-based routing (for new emails)
    return addressResolver(email, env);
  }
});

Auto-Reply with Loop Prevention

The agent detects auto-replies to prevent infinite loops:
private isAutoReply(parsed: ParsedEmail): boolean {
  // Check headers
  for (const h of parsed.headers) {
    // Auto-Submitted header (RFC 3834)
    if (h["auto-submitted"] && h["auto-submitted"] !== "no") {
      return true;
    }
    // X-Auto-Response-Suppress header (Microsoft)
    if (h["x-auto-response-suppress"]) {
      return true;
    }
    // Precedence header
    if (["bulk", "junk", "list", "auto_reply"].includes(h.precedence)) {
      return true;
    }
  }

  // Check subject line
  const subject = parsed.subject.toLowerCase();
  return subject.includes("auto-reply") ||
         subject.includes("out of office") ||
         subject.includes("automatic reply");
}

Secure Reply Flow

When sending outbound emails, the agent signs headers:
await this.replyToEmail(email, {
  fromName: "My Email Agent",
  body: "Thank you for your email!",
  secret: this.env.EMAIL_SECRET
});
The reply includes signed headers:
X-Agent-Name: EmailAgent
X-Agent-ID: customer123
X-Agent-Sig: <HMAC signature>
X-Agent-Sig-Ts: <Unix timestamp>
When a reply comes back, the signature is verified before routing:
AttackProtection
Forged headersSignature verification
Replay attacksTimestamp expiration
Future timestampClock skew limit (5 min)
Timing attacksConstant-time comparison

Testing

Automated Test Suite

# Run all tests (functional + security)
npm test

# Run with verbose output
npm run test:verbose

# Run only security tests
npm run test:security
Sample output:
═════════════════════════════════════════════════════════════════
FUNCTIONAL TESTS
═════════════════════════════════════════════════════════════════
  ✅ PASS  Basic Email                    (12ms)
  ✅ PASS  Unicode Content                (6ms)
  ✅ PASS  Long Subject                   (6ms)
  ✅ PASS  Multiline Body                 (7ms)
  ✅ PASS  Special Characters             (11ms)
  Subtotal: 5/5 passed

═════════════════════════════════════════════════════════════════
SECURITY TESTS (Attack Bypass Attempts)
═════════════════════════════════════════════════════════════════
  🛡️ BLOCKED  Forged headers (no signature)       (4ms)
  🛡️ BLOCKED  Fake signature (random)             (4ms)
  🛡️ BLOCKED  Expired signature (31 days)         (2ms)
  🛡️ BLOCKED  SQL injection in agent ID           (3ms)
  🛡️ BLOCKED  Path traversal in agent ID          (2ms)
  Subtotal: 15/15 attacks blocked

🎉 All tests passed! Security defenses are working.

Manual Testing

# Run all test scenarios
npm run test-email

# Run specific scenario
npm run test-email -- --scenario basic

# Use custom agent ID
npm run test-email -- --scenario unicode --id my-custom-id
Available scenarios: basic, unicode, long-subject, multiline, special-chars

Running the Example

1

Install dependencies

npm install
2

Configure secret

Update wrangler.jsonc with a unique secret:
"vars": {
  "EMAIL_SECRET": "your-unique-secret-here"
}
For production, use Wrangler secrets:
wrangler secret put EMAIL_SECRET
3

Start development server

npm start
4

Run tests

npm test

Deployment

1

Set production secret

wrangler secret put EMAIL_SECRET
2

Deploy

npm run deploy
3

Configure email routing

In Cloudflare Dashboard:
  1. Go to https://dash.cloudflare.com/<account-id>/<domain>/email/routing/routes
  2. Add routing rules to point to your worker
4

Send emails

Send emails to addresses like:
  • support@yourdomain.com → EmailAgent with ID “support”
  • EmailAgent+urgent@yourdomain.com → EmailAgent with ID “urgent”

Security Tests

The test suite includes 15 attack scenarios:
  • Forged headers without signature
  • Random/fake signatures
  • Expired signatures
  • Future timestamps
  • Malformed timestamps
  • SQL injection payloads
  • Path traversal attempts
  • Header injection (newlines)
  • Unicode normalization attacks
  • Case manipulation
  • Long payload DoS attempts
  • Null byte injection
Run: npm run test:security

GitHub Webhook

Handle webhooks with signature verification

x402 Payments

HTTP payment gating with automatic payment

Workflows

Multi-step workflows with approval gates

Email Guide

In-depth guide to email routing

Build docs developers (and LLMs) love