跳转到内容
搜索文档

Email Agent 示例

最后更新 查看 MarkdownAgent 设置

Agents 可通过 Cloudflare Email Service 发送和接收邮件。本指南展示如何使用 Workers 绑定发送出站邮件、将入站邮件路由到 Agent,并安全处理后续回复。

前提条件

在 Agent 中使用邮件前,你需要:

  1. 已在 Cloudflare Email Service 中接入的域名。
  2. wrangler.jsonc 中配置 send_email 绑定,用于出站邮件。
  3. Email Service 路由规则,将入站邮件发送到 Worker。
  4. 可选:若需安全回复路由,配置 EMAIL_SECRET secret。

域设置

  1. 登录 Cloudflare 仪表板
  2. 前往 Compute & AI(计算与 AI) > Email Service
  3. 选择 Onboard Domain(接入域名) 并选择你的域名。
  4. 添加 DNS 记录(SPF 与 DKIM)以授权发送。

DNS 变更通常在 5–15 分钟内完成(使用 Cloudflare DNS 的域名),但全球传播最多可能需要 24 小时。

Wrangler 配置

向 Worker 添加 email binding:

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "send_email": [
    {
      "name": "EMAIL",
      "remote": true
    }
  ]
}
[[send_email]]
name = "EMAIL"
remote = true

remote = true 选项让你在 wrangler dev 本地开发期间调用真实的 Email Service API。

快速入门

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

export class EmailAgent extends Agent {
	@callable()
	async sendWelcomeEmail(to) {
		await this.sendEmail({
			binding: this.env.EMAIL,
			to,
			from: "[email protected]",
			replyTo: "[email protected]",
			subject: "Welcome to our service",
			text: "Thanks for signing up. Reply to this email if you need help.",
		});
	}

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

		console.log("Received email from:", email.from);
		console.log("Subject:", parsed.subject);

		await this.replyToEmail(email, {
			fromName: "Support Agent",
			body: "Thanks for your email! We received it.",
		});
	}
}

export default {
	async email(message, env) {
		await routeAgentEmail(message, env, {
			resolver: createAddressBasedEmailResolver("EmailAgent"),
		});
	},
};
import { Agent, callable, routeAgentEmail } from "agents";
import { createAddressBasedEmailResolver, type AgentEmail } from "agents/email";
import PostalMime from "postal-mime";

export class EmailAgent extends Agent {
	@callable()
	async sendWelcomeEmail(to: string) {
		await this.sendEmail({
			binding: this.env.EMAIL,
			to,
			from: "[email protected]",
			replyTo: "[email protected]",
			subject: "Welcome to our service",
			text: "Thanks for signing up. Reply to this email if you need help.",
		});
	}

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

		console.log("Received email from:", email.from);
		console.log("Subject:", parsed.subject);

		await this.replyToEmail(email, {
			fromName: "Support Agent",
			body: "Thanks for your email! We received it.",
		});
	}
}

export default {
	async email(message, env) {
		await routeAgentEmail(message, env, {
			resolver: createAddressBasedEmailResolver("EmailAgent"),
		});
	},
} satisfies ExportedHandler<Env>;

发送出站邮件

使用 sendEmail()

sendEmail() 通过你显式传入的 send_email 绑定发送出站邮件。它会自动向每条消息注入 Agent 路由头(X-Agent-NameX-Agent-ID),并可选择用 HMAC-SHA256 签名,以便回复能路由回同一 Agent 实例。

class MyAgent extends Agent {
	@callable()
	async sendReceipt(to, orderId) {
		const result = await this.sendEmail({
			binding: this.env.EMAIL,
			to,
			from: { email: "[email protected]", name: "Billing Bot" },
			replyTo: "[email protected]",
			subject: `Receipt for order ${orderId}`,
			text: `Your receipt for order ${orderId} is ready.`,
			secret: this.env.EMAIL_SECRET,
		});

		return result.messageId;
	}
}
class MyAgent extends Agent {
	@callable()
	async sendReceipt(to: string, orderId: string) {
		const result = await this.sendEmail({
			binding: this.env.EMAIL,
			to,
			from: { email: "[email protected]", name: "Billing Bot" },
			replyTo: "[email protected]",
			subject: `Receipt for order ${orderId}`,
			text: `Your receipt for order ${orderId} is ready.`,
			secret: this.env.EMAIL_SECRET,
		});

		return result.messageId;
	}
}

提供 secret 时,Agent 会签名路由头,使经 createSecureReplyEmailResolver 验证的回复能路由回同一 Agent 实例。

若希望收件人继续与同一 Agent 对话,将 replyTo 设为会路由回 Worker 的邮箱。

路由入站邮件

Resolver 决定哪一 Agent 实例接收入站邮件。按用例选择合适的 resolver。

对于基本的 Email Service 收发,createAddressBasedEmailResolver() 已足够。下文的安全回复 resolver 是可选的,专用于 Agents SDK 回复签名,并非 Email Service 的硬性要求。

createAddressBasedEmailResolver

推荐用于入站邮件。根据收件地址路由邮件。

import { createAddressBasedEmailResolver } from "agents/email";

const resolver = createAddressBasedEmailResolver("EmailAgent");
import { createAddressBasedEmailResolver } from "agents/email";

const resolver = createAddressBasedEmailResolver("EmailAgent");

路由逻辑:

收件地址 Agent 名称 Agent ID
[email protected] EmailAgent(默认) support
[email protected] EmailAgent(默认) sales
[email protected] NotificationAgent user123

子地址格式(agent+id@domain)允许从单一邮件域名路由到不同 Agent 命名空间与实例。

createSecureReplyEmailResolver

用于带签名验证的回复流程。验证入站邮件是否为对你出站邮件的真实回复,防止攻击者伪造头将邮件路由到任意 Agent 实例。

import { createSecureReplyEmailResolver } from "agents/email";

const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET);
import { createSecureReplyEmailResolver } from "agents/email";

const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET);

当 Agent 使用 replyToEmail()sendEmail() 并传入 secret 发送邮件时,会用时间戳签名路由头。回复返回时,此 resolver 在路由前验证签名并检查是否过期。

选项:

const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET, {
	// 签名最大有效期(秒)(默认:30 天)
	maxAge: 7 * 24 * 60 * 60, // 7 天

	// 签名失败时的日志/调试回调
	onInvalidSignature: (email, reason) => {
		console.warn(`Invalid signature from ${email.from}: ${reason}`);
		// reason 可为:"missing_headers"、"expired"、"invalid"、"malformed_timestamp"
	},
});
const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET, {
	// 签名最大有效期(秒)(默认:30 天)
	maxAge: 7 * 24 * 60 * 60, // 7 天

	// 签名失败时的日志/调试回调
	onInvalidSignature: (email, reason) => {
		console.warn(`Invalid signature from ${email.from}: ${reason}`);
		// reason 可为:"missing_headers"、"expired"、"invalid"、"malformed_timestamp"
	},
});

适用场景: Agent 发起邮件对话且需要回复安全路由回同一 Agent 实例时。

createCatchAllEmailResolver

用于单实例路由。无论收件地址为何,都将所有邮件路由到指定 Agent 实例。

import { createCatchAllEmailResolver } from "agents/email";

const resolver = createCatchAllEmailResolver("EmailAgent", "default");
import { createCatchAllEmailResolver } from "agents/email";

const resolver = createCatchAllEmailResolver("EmailAgent", "default");

适用场景: 单一 Agent 实例处理所有邮件时(例如共享收件箱)。

组合 resolver

可组合多个 resolver 处理不同场景:

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);
			},

			// 处理未匹配任何路由规则的邮件
			onNoRoute: (email) => {
				console.warn(`No route found for email from ${email.from}`);
				email.setReject("Unknown recipient");
			},
		});
	},
};
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);
			},

			// 处理未匹配任何路由规则的邮件
			onNoRoute: (email) => {
				console.warn(`No route found for email from ${email.from}`);
				email.setReject("Unknown recipient");
			},
		});
	},
} satisfies ExportedHandler<Env>;

在 Agent 中处理邮件

AgentEmail 接口

调用 Agent 的 onEmail 方法时,会收到 AgentEmail 对象:

type AgentEmail = {
	from: string; // 发件人邮箱地址
	to: string; // 收件人邮箱地址
	headers: Headers; // 邮件头(subject、message-id 等)
	rawSize: number; // 原始邮件大小(字节)

	getRaw(): Promise<Uint8Array>; // 获取完整原始邮件内容
	reply(options): Promise<void>; // 发送回复
	forward(rcptTo, headers?): Promise<void>; // 转发邮件
	setReject(reason): void; // 拒收并附带原因
};

解析邮件内容

使用 postal-mime 等库解析原始邮件:

import PostalMime from "postal-mime";

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

		console.log("Subject:", parsed.subject);
		console.log("Text body:", parsed.text);
		console.log("HTML body:", parsed.html);
		console.log("Attachments:", parsed.attachments);
	}
}
import PostalMime from "postal-mime";

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

		console.log("Subject:", parsed.subject);
		console.log("Text body:", parsed.text);
		console.log("HTML body:", parsed.html);
		console.log("Attachments:", parsed.attachments);
	}
}

检测自动回复邮件

使用 isAutoReplyEmail() 检测自动回复,避免邮件循环:

import { isAutoReplyEmail } from "agents/email";
import PostalMime from "postal-mime";

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

		// 检测自动回复,避免重复响应
		if (isAutoReplyEmail(parsed.headers)) {
			console.log("Skipping auto-reply email");
			return;
		}

		// 处理邮件...
	}
}
import { isAutoReplyEmail } from "agents/email";
import PostalMime from "postal-mime";

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

		// 检测自动回复,避免重复响应
		if (isAutoReplyEmail(parsed.headers)) {
			console.log("Skipping auto-reply email");
			return;
		}

		// 处理邮件...
	}
}

这会检查标准 RFC 3834 头(Auto-SubmittedX-Auto-Response-SuppressPrecedence),判断邮件是否为自动回复。

回复邮件

使用 this.replyToEmail() 通过入站邮件的回复通道发送回复:

class MyAgent extends Agent {
	async onEmail(email) {
		await this.replyToEmail(email, {
			fromName: "Support Bot", // 发件人显示名
			subject: "Re: Your inquiry", // 可选,默认为 "Re: "
			body: "Thanks for contacting us!", // 邮件正文
			contentType: "text/plain", // 可选,默认为 "text/plain"
			headers: {
				// 可选自定义头
				"X-Custom-Header": "value",
			},
			secret: this.env.EMAIL_SECRET, // 可选,签名头以实现安全回复路由
		});
	}
}
class MyAgent extends Agent {
	async onEmail(email: AgentEmail) {
		await this.replyToEmail(email, {
			fromName: "Support Bot", // 发件人显示名
			subject: "Re: Your inquiry", // 可选,默认为 "Re: "
			body: "Thanks for contacting us!", // 邮件正文
			contentType: "text/plain", // 可选,默认为 "text/plain"
			headers: {
				// 可选自定义头
				"X-Custom-Header": "value",
			},
			secret: this.env.EMAIL_SECRET, // 可选,签名头以实现安全回复路由
		});
	}
}

延迟回复

replyToEmail() 需要实时的 AgentEmail 对象,因此只能在 onEmail() 内使用。若需稍后回复——例如定时任务、callable 方法或人工审批后——将发件人信息存入 state 并使用 sendEmail()

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

		this.setState({
			...this.state,
			pendingReply: {
				to: email.from,
				messageId: parsed.messageId,
				subject: parsed.subject,
			},
		});
	}

	@callable()
	async sendDelayedReply(body) {
		const { pendingReply } = this.state;
		if (!pendingReply) return;

		await this.sendEmail({
			binding: this.env.EMAIL,
			to: pendingReply.to,
			from: "[email protected]",
			subject: `Re: ${pendingReply.subject}`,
			text: body,
			inReplyTo: pendingReply.messageId,
			secret: this.env.EMAIL_SECRET,
		});
	}
}
class MyAgent extends Agent {
	async onEmail(email: AgentEmail) {
		const raw = await email.getRaw();
		const parsed = await PostalMime.parse(raw);

		this.setState({
			...this.state,
			pendingReply: {
				to: email.from,
				messageId: parsed.messageId,
				subject: parsed.subject,
			},
		});
	}

	@callable()
	async sendDelayedReply(body: string) {
		const { pendingReply } = this.state;
		if (!pendingReply) return;

		await this.sendEmail({
			binding: this.env.EMAIL,
			to: pendingReply.to,
			from: "[email protected]",
			subject: `Re: ${pendingReply.subject}`,
			text: body,
			inReplyTo: pendingReply.messageId,
			secret: this.env.EMAIL_SECRET,
		});
	}
}

inReplyTo 字段设置 In-Reply-To 头,使邮件客户端正确串接回复线程。secret 签名 Agent 路由头,使后续回复经 createSecureReplyEmailResolver 路由回此 Agent 实例。

转发邮件

class MyAgent extends Agent {
	async onEmail(email) {
		await email.forward("[email protected]");
	}
}
class MyAgent extends Agent {
	async onEmail(email: AgentEmail) {
		await email.forward("[email protected]");
	}
}

拒收邮件

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

错误处理

通过 sendEmail()replyToEmail() 发送邮件时,请处理以下常见错误:

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("发件人域名未验证。请在仪表板中验证。");
					break;
				case "E_RATE_LIMIT_EXCEEDED":
					console.error("超出速率限制。退避后重试。");
					break;
				case "E_DAILY_LIMIT_EXCEEDED":
					console.error("已达每日发送配额。");
					break;
				case "E_CONTENT_TOO_LARGE":
					console.error("邮件内容超出大小限制。");
					break;
				default:
					console.error("邮件发送失败:", 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("发件人域名未验证。请在仪表板中验证。");
					break;
				case "E_RATE_LIMIT_EXCEEDED":
					console.error("超出速率限制。退避后重试。");
					break;
				case "E_DAILY_LIMIT_EXCEEDED":
					console.error("已达每日发送配额。");
					break;
				case "E_CONTENT_TOO_LARGE":
					console.error("邮件内容超出大小限制。");
					break;
				default:
					console.error("邮件发送失败:", error.message);
			}
		}
	}
}

常见错误码

错误码 描述 解决方案
E_SENDER_NOT_VERIFIED 发件人域名/地址未验证 在 Cloudflare 仪表板中验证
E_RATE_LIMIT_EXCEEDED 达到发送速率限制 实现指数退避
E_DAILY_LIMIT_EXCEEDED 超出每日配额 等待配额重置或升级套餐
E_CONTENT_TOO_LARGE 邮件超出大小限制 减少附件或内容
E_RECIPIENT_NOT_ALLOWED 收件人不在允许列表 检查允许的目标地址
E_RECIPIENT_SUPPRESSED 收件人在抑制列表 从抑制列表移除
E_VALIDATION_ERROR 邮件格式无效 检查邮箱地址
E_TOO_MANY_RECIPIENTS 超过 50 个收件人 拆分为多次发送

安全回复路由

当 Agent 发送邮件并期望收到回复时,使用安全回复路由,防止攻击者伪造头将邮件路由到任意 Agent 实例。

工作原理

  1. 出站: 调用 replyToEmail()sendEmail() 并传入 secret 时,Agent 使用 HMAC-SHA256 签名路由头(X-Agent-NameX-Agent-ID)。
  2. 入站: createSecureReplyEmailResolver 在路由前验证签名。
  3. 强制: 若邮件经安全 resolver 路由,replyToEmail() 必须提供 secret(或显式传入 null 以选择退出)。

设置

  1. 向 Worker 添加 secret:

    {
      "$schema": "./node_modules/wrangler/config-schema.json",
      "vars": {
        "EMAIL_SECRET": "change-me-in-production"
      }
    }
    [vars]
    EMAIL_SECRET = "change-me-in-production"

    生产环境请改用 Wrangler secret:

    npx wrangler secret put EMAIL_SECRET
  2. 使用组合 resolver 模式:

    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);
    			},
    		});
    	},
    };
    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. 对出站邮件签名:

    class MyAgent extends Agent {
    	async onEmail(email) {
    		await this.replyToEmail(email, {
    			fromName: "My Agent",
    			body: "Thanks for your email!",
    			secret: this.env.EMAIL_SECRET, // 签名路由头
    		});
    	}
    }
    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, // 签名路由头
      });
     }
    }

强制行为

当邮件经 createSecureReplyEmailResolver 路由时,replyToEmail() 会强制签名:

secret 行为
"my-secret" 签名头(安全)
undefined(省略) 抛出错误 — 必须提供 secret 或显式选择退出
null 允许但不推荐 — 显式选择不签名

完整示例

以下是一个完整的 Email Service Agent,可发送出站邮件并处理安全回复:

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: "[email protected]",
			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: "[email protected]",
			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 参考

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>;

通过 Email Service 绑定发送出站邮件。自动注入 X-Agent-NameX-Agent-ID 头。提供 secret 时,用 HMAC-SHA256 签名头以实现安全回复路由。

选项 描述
binding send_email 绑定(例如 this.env.EMAIL)。必需。
to 收件地址、地址数组或 EmailAddress 对象
from 发件地址或 EmailAddress 对象
subject 邮件主题
text 纯文本正文(text/html 至少填一项)
html HTML 正文(text/html 至少填一项)
replyTo 回复地址或 EmailAddress 对象
cc 抄送地址、地址数组或 EmailAddress 对象
bcc 密送地址、地址数组或 EmailAddress 对象
inReplyTo 用于串接的 Message-ID(设置 In-Reply-To 头)
headers 额外自定义头(与 Agent 头冲突时以 Agent 头为准)
secret 用于 HMAC 签名 Agent 路由头的 secret

routeAgentEmail

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

根据 resolver 决策,将入站邮件路由到相应 Agent。

选项 描述
resolver 决定邮件路由到哪个 Agent 的函数
onNoRoute 可选回调,未找到路由信息时调用。可用于拒收或自定义处理。未提供时记录警告并丢弃邮件。

createSecureReplyEmailResolver

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

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

创建带签名验证的回复路由 resolver。

选项 描述
secret HMAC 验证密钥(须与签名时使用的密钥一致)
maxAge 签名最大有效期(秒)(默认:30 天 / 2592000 秒)
onInvalidSignature 签名验证失败时的可选日志回调

signAgentHeaders

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

手动签名 Agent 路由头。返回包含 X-Agent-NameX-Agent-IDX-Agent-SigX-Agent-Sig-Ts 的对象。

适用于通过外部服务发送邮件同时保持安全回复路由。签名含时间戳,默认 30 天内有效。

后续步骤

Webhook

接收来自外部服务的事件。

这篇文档对您有帮助吗?