Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Email

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

先决条件

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

  1. 一个已加入 Cloudflare Email Service 的域名。
  2. 在 wrangler.jsonc 中为出站邮件配置一个 send_email binding。
  3. 一条将入站邮件发送到你的 Worker 的 Email Service 路由规则。
  4. 可选:如果你想要安全的回复路由,设置一个 EMAIL_SECRET secret。

域名设置

  1. 登录 Cloudflare 仪表板 ↗。
  2. 转到 Compute & AI > Email Service。
  3. 选择 Onboard Domain 并选择你的域名。
  4. 添加 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 NameAgent ID
[email protected]EmailAgent (默认)support
[email protected]EmailAgent (默认)sales
[email protected]NotificationAgentuser123

子地址格式(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 实例。

工作原理

  1. 出站: 当你使用 secret 调用 replyToEmail() 或 sendEmail() 时,agent 使用 HMAC-SHA256 对路由头(X-Agent-Name、X-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"
  }
}

TOML

[vars]
EMAIL_SECRET = "change-me-in-production"

对于生产环境,使用 Wrangler secrets: 终端窗口

npx wrangler secret put EMAIL_SECRET
  1. 使用组合 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);
      },
    });
  },
};

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

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 对头签名以实现安全回复路由。

选项描述
bindingsend_email binding(例如,this.env.EMAIL)。必需。
to接收者地址或地址数组
from发件人地址,或 \{ email, name \} 对象
subject邮件主题行
text纯文本主体(text/html 至少需要一个)
htmlHTML 主体(text/html 至少需要一个)
replyTo接收者的回复地址
ccCC 接收者
bccBCC 接收者
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 参考。