Agent 可以通过 Cloudflare Email Service 发送和接收邮件。本指南展示如何使用 Workers binding 发送出站邮件、将入站邮件路由到 Agent,以及如何安全地处理后续回复。
先决条件
在 Agent 中使用邮件之前,你需要:
- 一个已加入 Cloudflare Email Service 的域名。
- 在
wrangler.jsonc中为出站邮件配置一个send_emailbinding。 - 一条将入站邮件发送到你的 Worker 的 Email Service 路由规则。
- 可选:如果你想要安全的回复路由,设置一个
EMAIL_SECRETsecret。
域名设置
- 登录 Cloudflare 仪表板 ↗。
- 转到 Compute & AI > Email Service。
- 选择 Onboard Domain 并选择你的域名。
- 添加 DNS 记录(SPF 和 DKIM)以授权发送。
对于使用 Cloudflare DNS 的域名,DNS 更改通常在 5-15 分钟内完成,但全局传播最长可能需要 24 小时。
Wrangler 配置
向你的 Worker 添加邮件 binding:
JSONC
{
"$schema": "./node_modules/wrangler/config-schema.json",
"send_email": [
{
"name": "EMAIL",
"remote": true
}
]
}
TOML
[[send_email]]
name = "EMAIL"
remote = true
remote = true 选项让你在使用 wrangler dev 进行本地开发时调用真正的 Email Service API。
快速开始
JavaScript
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"),
});
},
};
Explain Code
TypeScript
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>;
Explain Code
发送出站邮件
使用 sendEmail()
sendEmail() 通过你显式传递的 send_email binding 发送出站邮件。它自动将 agent 路由头(X-Agent-Name、X-Agent-ID)注入到每条消息中,并可选地使用 HMAC-SHA256 对其签名,以便回复可以路由回同一个 agent 实例。
JavaScript
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;
}
}
Explain Code
TypeScript
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;
}
}
Explain Code
提供 secret 时,agent 对路由头进行签名,以便由 createSecureReplyEmailResolver 验证的回复路由回同一个 agent 实例。
将 replyTo 设置为路由回你的 Worker 的邮箱,当你希望接收者使用同一个 agent 继续对话时。
路由入站邮件
Resolver 决定哪个 Agent 实例接收传入的邮件。选择匹配你的用例的 resolver。
对于基本的 Email Service 发送和接收,createAddressBasedEmailResolver() 就足够了。下面的安全回复 resolver 是可选的,特定于 Agents SDK 回复签名,而不是 Email Service 本身的要求。
createAddressBasedEmailResolver
推荐用于入站邮件。基于接收者地址路由邮件。
JavaScript
import { createAddressBasedEmailResolver } from "agents/email";
const resolver = createAddressBasedEmailResolver("EmailAgent");
TypeScript
import { createAddressBasedEmailResolver } from "agents/email";
const resolver = createAddressBasedEmailResolver("EmailAgent");
路由逻辑:
| 接收者地址 | Agent Name | Agent ID |
|---|---|---|
| [email protected] | EmailAgent (默认) | support |
| [email protected] | EmailAgent (默认) | sales |
| [email protected] | NotificationAgent | user123 |
子地址格式(agent+id@domain)允许从单个邮件域路由到不同的 agent 命名空间和实例。
注意
接收者地址中的 Agent 类名不区分大小写匹配。邮件基础设施通常将地址转换为小写,因此 [email protected] 和 [email protected] 都路由到 NotificationAgent 类。
createSecureReplyEmailResolver
用于带签名验证的回复流程。验证传入的邮件是否是对你的出站邮件的真实回复,防止攻击者将邮件路由到任意 agent 实例。
JavaScript
import { createSecureReplyEmailResolver } from "agents/email";
const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET);
TypeScript
import { createSecureReplyEmailResolver } from "agents/email";
const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET);
当你的 agent 使用 replyToEmail() 或 sendEmail() 加上 secret 发送邮件时,它会用时间戳对路由头签名。当回复返回时,此 resolver 在路由前验证签名并检查它是否过期。
选项:
JavaScript
const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET, {
// Maximum age of signature in seconds (default: 30 days)
maxAge: 7 * 24 * 60 * 60, // 7 days
// Callback for logging/debugging signature failures
onInvalidSignature: (email, reason) => {
console.warn(`Invalid signature from ${email.from}: ${reason}`);
// reason can be: "missing_headers", "expired", "invalid", "malformed_timestamp"
},
});
Explain Code
TypeScript
const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET, {
// Maximum age of signature in seconds (default: 30 days)
maxAge: 7 * 24 * 60 * 60, // 7 days
// Callback for logging/debugging signature failures
onInvalidSignature: (email, reason) => {
console.warn(`Invalid signature from ${email.from}: ${reason}`);
// reason can be: "missing_headers", "expired", "invalid", "malformed_timestamp"
},
});
Explain Code
何时使用: 如果你的 agent 发起邮件对话,并且你需要回复安全地路由回同一个 agent 实例。
createCatchAllEmailResolver
用于单实例路由。无论接收者地址如何,都将所有邮件路由到特定的 agent 实例。
JavaScript
import { createCatchAllEmailResolver } from "agents/email";
const resolver = createCatchAllEmailResolver("EmailAgent", "default");
TypeScript
import { createCatchAllEmailResolver } from "agents/email";
const resolver = createCatchAllEmailResolver("EmailAgent", "default");
何时使用: 当你有一个处理所有邮件的单个 agent 实例时(例如,共享收件箱)。
组合 resolver
你可以组合 resolver 来处理不同的场景:
JavaScript
export default {
async email(message, env) {
const secureReplyResolver = createSecureReplyEmailResolver(
env.EMAIL_SECRET,
);
const addressResolver = createAddressBasedEmailResolver("EmailAgent");
await routeAgentEmail(message, env, {
resolver: async (email, env) => {
// First, check if this is a signed reply
const replyRouting = await secureReplyResolver(email, env);
if (replyRouting) return replyRouting;
// Otherwise, route based on recipient address
return addressResolver(email, env);
},
// Handle emails that do not match any routing rule
onNoRoute: (email) => {
console.warn(`No route found for email from ${email.from}`);
email.setReject("Unknown recipient");
},
});
},
};
Explain Code
TypeScript
export default {
async email(message, env) {
const secureReplyResolver = createSecureReplyEmailResolver(
env.EMAIL_SECRET,
);
const addressResolver = createAddressBasedEmailResolver("EmailAgent");
await routeAgentEmail(message, env, {
resolver: async (email, env) => {
// First, check if this is a signed reply
const replyRouting = await secureReplyResolver(email, env);
if (replyRouting) return replyRouting;
// Otherwise, route based on recipient address
return addressResolver(email, env);
},
// Handle emails that do not match any routing rule
onNoRoute: (email) => {
console.warn(`No route found for email from ${email.from}`);
email.setReject("Unknown recipient");
},
});
},
} satisfies ExportedHandler<Env>;
Explain Code
在你的 Agent 中处理邮件
AgentEmail 接口
当你的 agent 的 onEmail 方法被调用时,它会接收一个 AgentEmail 对象:
TypeScript
type AgentEmail = {
from: string; // Sender's email address
to: string; // Recipient's email address
headers: Headers; // Email headers (subject, message-id, etc.)
rawSize: number; // Size of the raw email in bytes
getRaw(): Promise<Uint8Array>; // Get the full raw email content
reply(options): Promise<void>; // Send a reply
forward(rcptTo, headers?): Promise<void>; // Forward the email
setReject(reason): void; // Reject the email with a reason
};
Explain Code
解析邮件内容
使用 postal-mime ↗ 等库来解析原始邮件:
JavaScript
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);
}
}
Explain Code
TypeScript
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);
}
}
Explain Code
检测自动回复邮件
使用 isAutoReplyEmail() 检测自动回复邮件并避免邮件循环:
JavaScript
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);
// Detect auto-reply emails to avoid sending duplicate responses
if (isAutoReplyEmail(parsed.headers)) {
console.log("Skipping auto-reply email");
return;
}
// Process the email...
}
}
Explain Code
TypeScript
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);
// Detect auto-reply emails to avoid sending duplicate responses
if (isAutoReplyEmail(parsed.headers)) {
console.log("Skipping auto-reply email");
return;
}
// Process the email...
}
}
Explain Code
这会检查标准的 RFC 3834 头(Auto-Submitted、X-Auto-Response-Suppress、Precedence),它们指示邮件是自动回复。
回复邮件
使用 this.replyToEmail() 通过入站邮件的回复通道发送回复:
JavaScript
class MyAgent extends Agent {
async onEmail(email) {
await this.replyToEmail(email, {
fromName: "Support Bot", // Display name for the sender
subject: "Re: Your inquiry", // Optional, defaults to "Re: "
body: "Thanks for contacting us!", // Email body
contentType: "text/plain", // Optional, defaults to "text/plain"
headers: {
// Optional custom headers
"X-Custom-Header": "value",
},
secret: this.env.EMAIL_SECRET, // Optional, signs headers for secure reply routing
});
}
}
Explain Code
TypeScript
class MyAgent extends Agent {
async onEmail(email: AgentEmail) {
await this.replyToEmail(email, {
fromName: "Support Bot", // Display name for the sender
subject: "Re: Your inquiry", // Optional, defaults to "Re: "
body: "Thanks for contacting us!", // Email body
contentType: "text/plain", // Optional, defaults to "text/plain"
headers: {
// Optional custom headers
"X-Custom-Header": "value",
},
secret: this.env.EMAIL_SECRET, // Optional, signs headers for secure reply routing
});
}
}
Explain Code
延迟回复
replyToEmail() 需要一个活动的 AgentEmail 对象,因此它仅在 onEmail() 内部工作。如果你需要稍后回复 — 来自计划任务、callable 方法或人工审核之后 — 在状态中存储发送者信息,并使用 sendEmail():
JavaScript
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,
});
}
}
Explain Code
TypeScript
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,
});
}
}
Explain Code
inReplyTo 字段设置 In-Reply-To 头,以便邮件客户端正确地将回复线程化。secret 对 agent 路由头签名,以便后续回复通过 createSecureReplyEmailResolver 路由回此 agent 实例。
转发邮件
JavaScript
class MyAgent extends Agent {
async onEmail(email) {
await email.forward("[email protected]");
}
}
TypeScript
class MyAgent extends Agent {
async onEmail(email: AgentEmail) {
await email.forward("[email protected]");
}
}
拒绝邮件
JavaScript
class MyAgent extends Agent {
async onEmail(email) {
if (isSpam(email)) {
email.setReject("Message rejected as spam");
return;
}
// Process the email...
}
}
TypeScript
class MyAgent extends Agent {
async onEmail(email: AgentEmail) {
if (isSpam(email)) {
email.setReject("Message rejected as spam");
return;
}
// Process the email...
}
}
错误处理
通过 sendEmail() 或 replyToEmail() 发送邮件时,处理这些常见错误:
JavaScript
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);
}
}
}
}
Explain Code
TypeScript
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);
}
}
}
}
Explain Code
常见错误代码
| 错误代码 | 描述 | 解决方案 |
|---|---|---|
| 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 实例。
工作原理
- 出站: 当你使用
secret调用replyToEmail()或sendEmail()时,agent 使用 HMAC-SHA256 对路由头(X-Agent-Name、X-Agent-ID)签名。 - 入站:
createSecureReplyEmailResolver在路由前验证签名。 - 强制执行: 如果邮件通过安全 resolver 路由,则
replyToEmail()需要一个 secret(或显式null退出)。
设置
- 向你的 Worker 添加 secret:
- wrangler.jsonc
- wrangler.toml JSONC
{
"$schema": "./node_modules/wrangler/config-schema.json",
"vars": {
"EMAIL_SECRET": "change-me-in-production"
}
}
TOML
[vars]
EMAIL_SECRET = "change-me-in-production"
对于生产环境,使用 Wrangler secrets: 终端窗口
npx wrangler secret put EMAIL_SECRET
- 使用组合 resolver 模式:
- JavaScript
- TypeScript JavaScript
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);
},
});
},
};
Explain Code TypeScript
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>;
Explain Code 3. 对出站邮件签名:
- JavaScript
- TypeScript JavaScript
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
});
}
}
TypeScript
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
});
}
}
强制执行行为
当邮件通过 createSecureReplyEmailResolver 路由时,replyToEmail() 方法强制执行签名:
| secret 值 | 行为 |
|---|---|
| “my-secret” | 对头签名(安全) |
| undefined(省略) | 抛出错误 - 必须提供 secret 或显式退出 |
| null | 允许但不推荐 - 显式退出签名 |
完整示例
这是一个完整的 Email Service agent,它发送出站邮件并处理安全回复:
JavaScript
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");
},
});
},
};
Explain Code
TypeScript
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>;
Explain Code
API 参考
sendEmail
TypeScript
async sendEmail(options: {
binding: EmailSendBinding;
to: string | string[];
from: string | { email: string; name?: string };
subject: string;
text?: string;
html?: string;
replyTo?: string | { email: string; name?: string };
cc?: string | string[];
bcc?: string | string[];
inReplyTo?: string;
headers?: Record<string, string>;
secret?: string;
}): Promise<EmailSendResult>;
Explain Code
通过 Email Service binding 发送出站邮件。自动注入 X-Agent-Name 和 X-Agent-ID 头。当提供 secret 时,使用 HMAC-SHA256 对头签名以实现安全回复路由。
| 选项 | 描述 |
|---|---|
| binding | send_email binding(例如,this.env.EMAIL)。必需。 |
| to | 接收者地址或地址数组 |
| from | 发件人地址,或 \{ email, name \} 对象 |
| subject | 邮件主题行 |
| text | 纯文本主体(text/html 至少需要一个) |
| html | HTML 主体(text/html 至少需要一个) |
| replyTo | 接收者的回复地址 |
| cc | CC 接收者 |
| bcc | BCC 接收者 |
| inReplyTo | 用于线程化的 Message-ID(设置 In-Reply-To 头) |
| headers | 额外的自定义头(如果冲突,agent 头优先) |
| secret | 用于 HMAC 签名 agent 路由头的 secret |
routeAgentEmail
TypeScript
function routeAgentEmail<Env>(
email: ForwardableEmailMessage,
env: Env,
options: {
resolver: EmailResolver;
onNoRoute?: (email: ForwardableEmailMessage) => void | Promise<void>;
},
): Promise<void>;
根据 resolver 的决策将传入邮件路由到适当的 Agent。
| 选项 | 描述 |
|---|---|
| resolver | 决定将邮件路由到哪个 agent 的函数 |
| onNoRoute | 当未找到路由信息时调用的可选回调。使用此项拒绝邮件或执行自定义处理。如果未提供,将记录警告并丢弃邮件。 |
createSecureReplyEmailResolver
TypeScript
function createSecureReplyEmailResolver(
secret: string,
options?: {
maxAge?: number;
onInvalidSignature?: (
email: ForwardableEmailMessage,
reason: SignatureFailureReason,
) => void;
},
): EmailResolver;
type SignatureFailureReason =
| "missing_headers"
| "expired"
| "invalid"
| "malformed_timestamp";
Explain Code
创建一个用于带签名验证的邮件回复路由的 resolver。
| 选项 | 描述 |
|---|---|
| secret | 用于 HMAC 验证的 secret 密钥(必须与用于签名的密钥匹配) |
| maxAge | 签名最大有效时间(秒)(默认:30 天 / 2592000 秒) |
| onInvalidSignature | 签名验证失败时记录的可选回调 |
signAgentHeaders
TypeScript
function signAgentHeaders(
secret: string,
agentName: string,
agentId: string,
): Promise<Record<string, string>>;
手动签名 agent 路由头。返回一个带有 X-Agent-Name、X-Agent-ID、X-Agent-Sig 和 X-Agent-Sig-Ts 头的对象。
在通过外部服务发送邮件同时维护安全回复路由时很有用。签名包括时间戳,默认有效期为 30 天。
下一步
HTTP and SSE 在你的 Agent 中处理 HTTP 请求。
Webhooks 接收来自外部服务的事件。
Agents API Agents SDK 的完整 API 参考。