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-NameX-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-SubmittedX-Auto-Response-SuppressPrecedence),它们指示邮件是自动回复。

回复邮件

使用 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-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"
  }
}

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-NameX-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-NameX-Agent-IDX-Agent-SigX-Agent-Sig-Ts 头的对象。

在通过外部服务发送邮件同时维护安全回复路由时很有用。签名包括时间戳,默认有效期为 30 天。

下一步

HTTP and SSE 在你的 Agent 中处理 HTTP 请求。

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

Agents API Agents SDK 的完整 API 参考。