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
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
1. Secure Reply Routing (Recommended for Replies)
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.com → agentName: "localpart", agentId: "subaddress"
Without sub-addresses: localpart@domain.com → agentName: 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:
Attack Protection Forged headers Signature verification Replay attacks Timestamp expiration Future timestamp Clock skew limit (5 min) Timing attacks Constant-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
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
Deployment
Set production secret
wrangler secret put EMAIL_SECRET
Configure email routing
In Cloudflare Dashboard:
Go to https://dash.cloudflare.com/<account-id>/<domain>/email/routing/routes
Add routing rules to point to your worker
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