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.
Overview
Cloudflare Agents provides utilities for routing incoming emails to Agent instances with support for address-based routing, secure reply flows, and catch-all patterns.
import { routeAgentEmail, createAddressBasedEmailResolver } from "agents";
export default {
async email(message, env) {
await routeAgentEmail(message, env, {
resolver: createAddressBasedEmailResolver("EmailAgent")
});
}
};
routeAgentEmail()
Route an email to the appropriate Agent.
email
ForwardableEmailMessage
required
The email to route (from Email Workers)
Environment containing Agent bindings
options
EmailRoutingOptions<Env>
required
resolver
EmailResolver<Env>
required
Function that determines which Agent to route to
onNoRoute
(email: ForwardableEmailMessage) => void | Promise<void>
Called when no routing information is found. If not provided, a warning is logged and the email is dropped.
export default {
async email(message: ForwardableEmailMessage, env: Env) {
await routeAgentEmail(message, env, {
resolver: createAddressBasedEmailResolver("SupportAgent"),
onNoRoute: async (email) => {
console.warn("No route for:", email.from, "→", email.to);
email.setReject("Invalid recipient");
}
});
}
};
Returns: Promise<void>
Email Resolvers
createAddressBasedEmailResolver()
Route based on email address (sub-address or local part).
Default agent name to use if email doesn’t contain sub-address
const resolver = createAddressBasedEmailResolver("SupportAgent");
Routing patterns:
support+ticket123@example.com → SupportAgent instance ticket123
support@example.com → SupportAgent instance support
agent+room@example.com → agent instance room
Returns: EmailResolver<Env>
createSecureReplyEmailResolver()
Route secure reply emails with signature verification.
Secret key for HMAC verification (must match signAgentHeaders)
options
SecureReplyResolverOptions
Maximum signature age in seconds (default: 30 days)
Called when signature verification fails
const secureResolver = createSecureReplyEmailResolver(env.EMAIL_SECRET, {
maxAge: 7 * 24 * 60 * 60, // 7 days
onInvalidSignature: (email, reason) => {
console.warn(`Invalid signature from ${email.from}: ${reason}`);
}
});
Returns: EmailResolver<Env>
Use signAgentHeaders() when sending outbound emails to enable secure reply routing.
createCatchAllEmailResolver()
Route all emails to a single Agent instance.
const resolver = createCatchAllEmailResolver("InboxAgent", "main");
Returns: EmailResolver<Env>
Combining Resolvers
Try multiple resolvers in sequence:
export default {
async email(message: ForwardableEmailMessage, env: Env) {
const secureResolver = createSecureReplyEmailResolver(env.EMAIL_SECRET);
const addressResolver = createAddressBasedEmailResolver("SupportAgent");
await routeAgentEmail(message, env, {
resolver: async (email, env) => {
// Try secure reply routing first
const replyRouting = await secureResolver(email, env);
if (replyRouting) return replyRouting;
// Fall back to address-based routing
return addressResolver(email, env);
}
});
}
};
Secure Reply Flow
Signing Outbound Emails
Use signAgentHeaders() to sign emails for secure reply routing:
Secret key for HMAC signing (store in environment variables)
Agent class name (kebab-case)
import { signAgentHeaders } from "agents/email";
const headers = await signAgentHeaders(
env.EMAIL_SECRET,
"support-agent",
this.name
);
// Use headers when sending email
// Headers: X-Agent-Name, X-Agent-ID, X-Agent-Sig, X-Agent-Sig-Ts
Returns: Promise<Record<string, string>>
replyToEmail()
Reply to an email from within an Agent:
Email subject (defaults to “Re: original subject”)
contentType
string
default:"text/plain"
MIME content type
Secret for signing headers. Required if email was routed via createSecureReplyEmailResolver. Pass null to opt out.
class SupportAgent extends Agent {
async onEmail(email: AgentEmail) {
await this.replyToEmail(email, {
fromName: "Support Team",
body: "Thank you for your message!",
secret: this.env.EMAIL_SECRET
});
}
}
Returns: Promise<void>
If the email was routed via createSecureReplyEmailResolver, you must pass a secret to sign replies. Otherwise, replies cannot be routed back securely.
Email Utilities
isAutoReplyEmail()
Check if an email is an auto-reply (to avoid reply loops).
Headers array from postal-mime or similar
import { isAutoReplyEmail } from "agents/email";
import PostalMime from "postal-mime";
class EmailAgent extends Agent {
async onEmail(email: AgentEmail) {
const raw = await email.getRaw();
const parser = new PostalMime();
const parsed = await parser.parse(raw);
if (isAutoReplyEmail(parsed.headers)) {
console.log("Skipping auto-reply");
return;
}
// Process email
}
}
Returns: boolean
Checks for:
Auto-Submitted header (RFC 3834)
X-Auto-Response-Suppress header
Precedence: bulk/junk/list header
Email Handler
onEmail()
Override to handle incoming emails in your Agent:
import type { AgentEmail } from "agents/email";
import PostalMime from "postal-mime";
class SupportAgent extends Agent {
async onEmail(email: AgentEmail) {
// Parse email
const raw = await email.getRaw();
const parser = new PostalMime();
const parsed = await parser.parse(raw);
// Skip auto-replies
if (isAutoReplyEmail(parsed.headers)) {
return;
}
// Extract content
const subject = email.headers.get("subject") ?? "No subject";
const text = parsed.text ?? "";
console.log(`Email from ${email.from}: ${subject}`);
console.log(`Body: ${text}`);
// Reply
await this.replyToEmail(email, {
fromName: "Support Bot",
body: `Thanks for your message about: ${subject}`,
secret: this.env.EMAIL_SECRET
});
}
}
AgentEmail Type
The AgentEmail object passed to onEmail():
Email headersconst subject = email.headers.get("subject");
const messageId = email.headers.get("message-id");
Size of the raw email in bytes
getRaw
() => Promise<Uint8Array>
required
Get the raw email contentconst raw = await email.getRaw();
const parser = new PostalMime();
const parsed = await parser.parse(raw);
reply
(options) => Promise<void>
required
Send a reply (use replyToEmail() instead for automatic header signing)
forward
(rcptTo: string, headers?: Headers) => Promise<void>
required
Forward the email to another address
setReject
(reason: string) => void
required
Reject the email with a reason
Full Example
// src/index.ts
import {
routeAgentEmail,
createSecureReplyEmailResolver,
createAddressBasedEmailResolver,
type AgentEmail,
isAutoReplyEmail
} from "agents";
import PostalMime from "postal-mime";
export class SupportAgent extends Agent {
async onEmail(email: AgentEmail) {
// Parse email
const raw = await email.getRaw();
const parser = new PostalMime();
const parsed = await parser.parse(raw);
// Skip auto-replies
if (isAutoReplyEmail(parsed.headers)) {
return;
}
const subject = email.headers.get("subject") ?? "No subject";
const text = parsed.text ?? "";
console.log(`Ticket from ${email.from}: ${subject}`);
// Store in state
this.setState({
...this.state,
lastEmail: {
from: email.from,
subject,
body: text,
receivedAt: Date.now()
}
});
// Reply
await this.replyToEmail(email, {
fromName: "Support Team",
subject: `Re: ${subject}`,
body: `Thank you for contacting support. Your ticket has been created.\n\nOriginal message:\n${text}`,
secret: this.env.EMAIL_SECRET
});
}
}
export default {
async email(message: ForwardableEmailMessage, env: Env) {
const secureResolver = createSecureReplyEmailResolver(env.EMAIL_SECRET, {
onInvalidSignature: (email, reason) => {
console.warn(`Invalid signature: ${reason}`);
}
});
const addressResolver = createAddressBasedEmailResolver("SupportAgent");
await routeAgentEmail(message, env, {
resolver: async (email, env) => {
// Try secure reply first
const routing = await secureResolver(email, env);
if (routing) return routing;
// Fall back to address-based
return addressResolver(email, env);
},
onNoRoute: (email) => {
console.warn("No route for:", email.to);
email.setReject("Invalid recipient");
}
});
}
};
wrangler.jsonc Configuration
{
"name": "email-agent",
"main": "src/index.ts",
"compatibility_date": "2026-01-28",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [
{
"name": "SUPPORT_AGENT",
"class_name": "SupportAgent",
"script_name": "email-agent"
}
]
},
"vars": {
"EMAIL_SECRET": "your-secret-here"
}
}
Security
Signature Verification
Signatures prevent attackers from spoofing email headers to route emails to arbitrary agents:
// ✅ Secure - verifies HMAC signature
const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET);
// ❌ Insecure - trusts attacker-controlled headers (REMOVED)
const resolver = createHeaderBasedEmailResolver(); // Error: removed
Signature Expiration
Signatures expire after maxAge (default: 30 days):
const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET, {
maxAge: 7 * 24 * 60 * 60 // 7 days
});
Secret Management
Store secrets in environment variables:
wrangler secret put EMAIL_SECRET
Or in .dev.vars for local development:
EMAIL_SECRET=your-secret-here
Best Practices
Use Secure Resolvers
// ✅ Good - secure reply routing
const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET);
// ❌ Bad - address-based only (no signature verification)
const resolver = createAddressBasedEmailResolver("Agent");
Check Auto-Replies
// ✅ Good - prevent reply loops
if (isAutoReplyEmail(parsed.headers)) {
return;
}
// ❌ Bad - might create reply loops
// (no auto-reply check)
Handle No Route
// ✅ Good - reject invalid emails
await routeAgentEmail(message, env, {
resolver,
onNoRoute: (email) => {
email.setReject("Invalid recipient");
}
});
// ❌ Bad - silently drop emails
await routeAgentEmail(message, env, { resolver });
Sign Replies
// ✅ Good - sign for secure routing
await this.replyToEmail(email, {
fromName: "Bot",
body: "Reply",
secret: this.env.EMAIL_SECRET
});
// ❌ Bad - no signature (replies can't be securely routed)
await this.replyToEmail(email, {
fromName: "Bot",
body: "Reply",
secret: null
});