Email agent·续

Forwarding emails

class MyAgent extends Agent {
    async onEmail(email) {
        await email.forward("admin@example.com");
    }
}
class MyAgent extends Agent {
    async onEmail(email: AgentEmail) {
        await email.forward("admin@example.com");
    }
}

Rejecting emails

class MyAgent extends Agent {
    async onEmail(email) {
        if (isSpam(email)) {
            email.setReject("Message rejected as spam");
            return;
        }
        // Process the email...
    }
}
class MyAgent extends Agent {
    async onEmail(email: AgentEmail) {
        if (isSpam(email)) {
            email.setReject("Message rejected as spam");
            return;
        }
        // Process the email...
    }
}

Error handling

When sending emails via sendEmail() or replyToEmail(), handle these common errors:

class MyAgent extends Agent {
    async onEmail(email) {
        try {
            await this.replyToEmail(email, {
                fromName: "Support Bot",
                body: "Thanks for your email!",
            });
        } catch (error) {
            switch (error.code) {
                case "E_SENDER_NOT_VERIFIED":
                    console.error("Sender domain not verified. Verify in dashboard.");
                    break;
                case "E_RATE_LIMIT_EXCEEDED":
                    console.error("Rate limit exceeded. Back off and retry.");
                    break;
                case "E_DAILY_LIMIT_EXCEEDED":
                    console.error("Daily sending quota reached.");
                    break;
                case "E_CONTENT_TOO_LARGE":
                    console.error("Email content exceeds size limit.");
                    break;
                default:
                    console.error("Email sending failed:", error.message);
            }
        }
    }
}
class MyAgent extends Agent {
    async onEmail(email: AgentEmail) {
        try {
            await this.replyToEmail(email, {
                fromName: "Support Bot",
                body: "Thanks for your email!",
            });
        } catch (error) {
            switch (error.code) {
                case "E_SENDER_NOT_VERIFIED":
                    console.error("Sender domain not verified. Verify in dashboard.");
                    break;
                case "E_RATE_LIMIT_EXCEEDED":
                    console.error("Rate limit exceeded. Back off and retry.");
                    break;
                case "E_DAILY_LIMIT_EXCEEDED":
                    console.error("Daily sending quota reached.");
                    break;
                case "E_CONTENT_TOO_LARGE":
                    console.error("Email content exceeds size limit.");
                    break;
                default:
                    console.error("Email sending failed:", error.message);
            }
        }
    }
}

Common error codes

Error Code Description Solution
E_SENDER_NOT_VERIFIED Sender domain/address not verified Verify in Cloudflare dashboard
E_RATE_LIMIT_EXCEEDED Sending rate limit reached Implement exponential backoff
E_DAILY_LIMIT_EXCEEDED Daily quota exceeded Wait for quota reset or upgrade plan
E_CONTENT_TOO_LARGE Email exceeds size limit Reduce attachments or content
E_RECIPIENT_NOT_ALLOWED Recipient not in allowed list Check allowed destination addresses
E_RECIPIENT_SUPPRESSED Recipient is on suppression list Remove from suppression list
E_VALIDATION_ERROR Invalid email format Check email addresses
E_TOO_MANY_RECIPIENTS More than 50 recipients Split into multiple sends

Secure reply routing

When your agent sends emails and expects replies, use secure reply routing to prevent attackers from forging headers to route emails to arbitrary agent instances.

How it works

  1. Outbound: When you call replyToEmail() or sendEmail() with a secret, the agent signs the routing headers ( X-Agent-Name, X-Agent-ID) using HMAC-SHA256.
  2. Inbound: createSecureReplyEmailResolver verifies the signature before routing.
  3. Enforcement: If an email was routed via the secure resolver, replyToEmail() requires a secret (or explicit null to opt-out).

Setup

  1. Store the signing key as a Wrangler secret. Do not put it in vars or commit it to source control:npmyarnpnpm
   npx wrangler secret put EMAIL_SECRET
   ```

yarn wrangler secret put EMAIL_SECRET ```

   pnpm wrangler secret put EMAIL_SECRET
   ```

2. Use the combined resolver pattern:

```js
   export default {
    async email(message, env) {
        const secureReplyResolver = createSecureReplyEmailResolver(
            env.EMAIL_SECRET,
        );
        const addressResolver = createAddressBasedEmailResolver("EmailAgent");

await routeAgentEmail(message, env, {
            resolver: async (email, env) => {
                const replyRouting = await secureReplyResolver(email, env);
                if (replyRouting) return replyRouting;
                return addressResolver(email, env);
            },
        });
    },
   };
   ```

```ts
   export default {
    async email(message, env) {
     const secureReplyResolver = createSecureReplyEmailResolver(
      env.EMAIL_SECRET,
     );
     const addressResolver = createAddressBasedEmailResolver("EmailAgent");

await routeAgentEmail(message, env, {
      resolver: async (email, env) => {
       const replyRouting = await secureReplyResolver(email, env);
       if (replyRouting) return replyRouting;
       return addressResolver(email, env);
      },
     });
    },
   } satisfies ExportedHandler<Env>;
   ```

3. Sign outbound emails:

```js
   class MyAgent extends Agent {
    async onEmail(email) {
        await this.replyToEmail(email, {
            fromName: "My Agent",
            body: "Thanks for your email!",
            secret: this.env.EMAIL_SECRET, // Signs the routing headers
        });
    }
   }
   ```

```ts
   class MyAgent extends Agent {
    async onEmail(email: AgentEmail) {
     await this.replyToEmail(email, {
      fromName: "My Agent",
      body: "Thanks for your email!",
      secret: this.env.EMAIL_SECRET, // Signs the routing headers
     });
    }
   }
   ```

### Enforcement behavior

When an email is routed via `createSecureReplyEmailResolver`, the `replyToEmail()` method enforces signing:

| `secret` value | Behavior |
| --- | --- |
| `"my-secret"` | Signs headers (secure) |
| `undefined` (omitted) | **Throws error** - must provide secret or explicit opt-out |
| `null` | Allowed but not recommended - explicitly opts out of signing |

## Complete example

Here is a complete Email Service agent that sends outbound mail and handles secure replies:

```js
import { Agent, callable, routeAgentEmail } from "agents";
import {
    createAddressBasedEmailResolver,
    createSecureReplyEmailResolver,
} from "agents/email";
import PostalMime from "postal-mime";

export class EmailAgent extends Agent {
    @callable()
    async sendWelcome(to) {
        return this.sendEmail({
            binding: this.env.EMAIL,
            to,
            from: "support@yourdomain.com",
            subject: "Welcome!",
            text: "Thanks for signing up.",
            secret: this.env.EMAIL_SECRET,
        });
    }

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

console.log(`Email from ${email.from}: ${parsed.subject}`);

const emails = this.state.emails || [];
        emails.push({
            from: email.from,
            subject: parsed.subject,
            receivedAt: new Date().toISOString(),
        });
        this.setState({ ...this.state, emails });

await this.replyToEmail(email, {
            fromName: "Support Bot",
            body: `Thanks for your email! We received: "${parsed.subject}"`,
            secret: this.env.EMAIL_SECRET,
        });
    }
}

export default {
    async email(message, env) {
        const secureReplyResolver = createSecureReplyEmailResolver(
            env.EMAIL_SECRET,
            {
                maxAge: 7 * 24 * 60 * 60, // 7 days
                onInvalidSignature: (email, reason) => {
                    console.warn(`Invalid signature from ${email.from}: ${reason}`);
                },
            },
        );
        const addressResolver = createAddressBasedEmailResolver("EmailAgent");

await routeAgentEmail(message, env, {
            resolver: async (email, env) => {
                const replyRouting = await secureReplyResolver(email, env);
                if (replyRouting) return replyRouting;
                return addressResolver(email, env);
            },
            onNoRoute: (email) => {
                console.warn(`No route found for email from ${email.from}`);
                email.setReject("Unknown recipient");
            },
        });
    },
};
import { Agent, callable, routeAgentEmail } from "agents";
import {
    createAddressBasedEmailResolver,
    createSecureReplyEmailResolver,
    type AgentEmail,
} from "agents/email";
import PostalMime from "postal-mime";

interface Env {
    EmailAgent: DurableObjectNamespace<EmailAgent>;
    EMAIL: SendEmail;
    EMAIL_SECRET: string;
}

export class EmailAgent extends Agent<Env> {
    @callable()
    async sendWelcome(to: string) {
        return this.sendEmail({
            binding: this.env.EMAIL,
            to,
            from: "support@yourdomain.com",
            subject: "Welcome!",
            text: "Thanks for signing up.",
            secret: this.env.EMAIL_SECRET,
        });
    }

async onEmail(email: AgentEmail) {
        const raw = await email.getRaw();
        const parsed = await PostalMime.parse(raw);

console.log(`Email from ${email.from}: ${parsed.subject}`);

const emails = this.state.emails || [];
        emails.push({
            from: email.from,
            subject: parsed.subject,
            receivedAt: new Date().toISOString(),
        });
        this.setState({ ...this.state, emails });

await this.replyToEmail(email, {
            fromName: "Support Bot",
            body: `Thanks for your email! We received: "${parsed.subject}"`,
            secret: this.env.EMAIL_SECRET,
        });
    }
}

export default {
    async email(message, env: Env) {
        const secureReplyResolver = createSecureReplyEmailResolver(
            env.EMAIL_SECRET,
            {
                maxAge: 7 * 24 * 60 * 60, // 7 days
                onInvalidSignature: (email, reason) => {
                    console.warn(`Invalid signature from ${email.from}: ${reason}`);
                },
            },
        );
        const addressResolver = createAddressBasedEmailResolver("EmailAgent");

await routeAgentEmail(message, env, {
            resolver: async (email, env) => {
                const replyRouting = await secureReplyResolver(email, env);
                if (replyRouting) return replyRouting;
                return addressResolver(email, env);
            },
            onNoRoute: (email) => {
                console.warn(`No route found for email from ${email.from}`);
                email.setReject("Unknown recipient");
            },
        });
    },
} satisfies ExportedHandler<Env>;

API reference

EmailAddress

interface EmailAddress {
    email: string;
    name?: string;
}

sendEmail

async sendEmail(options: {
    binding: EmailSendBinding;
    to: string | EmailAddress | (string | EmailAddress)[];
    from: string | EmailAddress;
    subject: string;
    text?: string;
    html?: string;
    replyTo?: string | EmailAddress;
    cc?: string | EmailAddress | (string | EmailAddress)[];
    bcc?: string | EmailAddress | (string | EmailAddress)[];
    inReplyTo?: string;
    headers?: Record<string, string>;
    secret?: string;
}): Promise<EmailSendResult>;

Send an outbound email through the Email Service binding. Automatically injects X-Agent-Name and X-Agent-ID headers. When secret is provided, signs headers with HMAC-SHA256 for secure reply routing.

Option Description
binding The send_email binding (for example, this.env.EMAIL). Required.
to Recipient address, array of addresses, or EmailAddress object(s)
from Sender address or EmailAddress object
subject Email subject line
text Plain text body (at least one of text/html required)
html HTML body (at least one of text/html required)
replyTo Reply-to address or EmailAddress object
cc CC recipient address, array of addresses, or EmailAddress object(s)
bcc BCC recipient address, array of addresses, or EmailAddress object(s)
inReplyTo Message-ID for threading (sets the In-Reply-To header)
headers Additional custom headers (agent headers take precedence if they collide)
secret Secret for HMAC signing of agent routing headers

routeAgentEmail

function routeAgentEmail<Env>(
    email: ForwardableEmailMessage,
    env: Env,
    options: {
        resolver: EmailResolver;
        onNoRoute?: (email: ForwardableEmailMessage) => void | Promise<void>;
    },
): Promise<void>;

Routes an incoming email to the appropriate Agent based on the resolver's decision.

Option Description
resolver Function that determines which agent to route the email to
onNoRoute Optional callback invoked when no routing information is found. Use this to reject the email or perform custom handling. If not provided, a warning is logged and the email is dropped.

createSecureReplyEmailResolver

function createSecureReplyEmailResolver(
    secret: string,
    options?: {
        maxAge?: number;
        onInvalidSignature?: (
            email: ForwardableEmailMessage,
            reason: SignatureFailureReason,
        ) => void;
    },
): EmailResolver;

type SignatureFailureReason =
    | "missing_headers"
    | "expired"
    | "invalid"
    | "malformed_timestamp";

Creates a resolver for routing email replies with signature verification.

Option Description
secret Secret key for HMAC verification (must match the key used to sign)
maxAge Maximum age of signature in seconds (default: 30 days / 2592000 seconds)
onInvalidSignature Optional callback for logging when signature verification fails

signAgentHeaders

function signAgentHeaders(
    secret: string,
    agentName: string,
    agentId: string,
): Promise<Record<string, string>>;

Manually sign agent routing headers. Returns an object with X-Agent-Name, X-Agent-ID, X-Agent-Sig, and X-Agent-Sig-Ts headers.

Useful when sending emails through external services while maintaining secure reply routing. The signature includes a timestamp and will be valid for 30 days by default.

Next steps

HTTP and SSE

Handle HTTP requests in your Agent.

Webhooks

Receive events from external services.

Agents API

Complete API reference for the Agents SDK.

Was this helpful?

YesNo

On this page

Email agent·续 - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/agents/examples/email-agent/#page","headline":"Email agent","description":"Build an agent that sends, receives, routes, and replies to email using Cloudflare Email Service and the Agents SDK.","url":"https://developers.cloudflare.com/agents/examples/email-agent/","inLanguage":"en","image":"https://developers.cloudflare.com/agents/examples/email-agent/og.png?v=ca77b2420210c79e","dateModified":"2026-08-17","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Build and deploy an AI-powered Slack bot on Cloudflare Workers using the Agents SDK. title: Slack agent image: https://developers.cloudflare.com/agents/examples/slack-agent/og.png?v=6b68e1af54a972c3


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/agents/llms.txt
Use this file to discover all available pages before exploring further.