← Создание агентов на Cloudflare / agents / examples
Email-агент
Agents могут отправлять и получать электронную почту с помощью Cloudflare Служба электронной почты. В этом руководстве показано, как отправлять исходящую почту через binding Workers, направлять входящую почту в Agents и безопасно обрабатывать ответные письма.
Предварительные требования
Прежде чем использовать email с Agents, вам понадобится:
- Домен, подключённый к Cloudflare Email Service.
- A
send_emailпривязку вwrangler.jsoncдля исходящей почты. - Правило маршрутизации Email Service, которое отправляет входящую почту в ваш Worker.
- Необязательно:
EMAIL_SECRETsecret, если нужна безопасная маршрутизация ответов.
Настройка домена
- Войдите в Cloudflare Dashboard ↗.
- Перейдите в Compute & AI > Служба электронной почты.
- Выберите Подключить домен и выберите свой домен.
- Добавьте записи DNS (SPF и DKIM), чтобы авторизовать отправку.
Изменения DNS для доменов, использующих Cloudflare DNS, обычно вступают в силу в течение 5-15 минут, но их глобальное распространение может занять до 24 часов.
Конфигурация Wrangler
Добавьте привязку email в свой Worker:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"send_email": [
{
"name": "EMAIL",
"remote": true
}
]
}[[send_email]]
name = "EMAIL"
remote = true remote = true опция позволяет вызывать реальный API Email Service во время локальной разработки с помощью wrangler dev.
быстрый старт
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 привязку, которую вы передаёте явно. Она автоматически добавляет заголовки маршрутизации агента (X-Agent-Name, X-Agent-ID) в каждое сообщение и при необходимости подписывает их с помощью HMAC-SHA256, чтобы ответы можно было маршрутизировать обратно к тому же экземпляру агента.
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 задан, агент подписывает заголовки маршрутизации, чтобы ответы, проверенные createSecureReplyEmailResolver route возвращается к тому же экземпляру агента.
Задайте replyTo в почтовый ящик, который направляет обратно на ваш Worker, если вы хотите, чтобы получатели продолжали разговор с тем же агентом.
Маршрутизация входящей почты
Резолверы определяют, какой экземпляр Agent получит входящее письмо. Выберите резолвер, который подходит вашему сценарию использования.
Для базовой отправки и получения через Email Service createAddressBasedEmailResolver() достаточно. Указанный ниже резолвер безопасного ответа необязателен и относится к подписанию ответов Agents SDK, а не является требованием самого Email Service.
createAddressBasedEmailResolver
Рекомендуется для входящей почты. Маршрутизирует письма по адресу получателя.
import { createAddressBasedEmailResolver } from "agents/email";
const resolver = createAddressBasedEmailResolver("EmailAgent");import { createAddressBasedEmailResolver } from "agents/email";
const resolver = createAddressBasedEmailResolver("EmailAgent");Логика маршрутизации:
| Адрес получателя | Имя агента | ID агента |
|---|---|---|
[email protected] |
EmailAgent (по умолчанию) |
support |
[email protected] |
EmailAgent (по умолчанию) |
sales |
[email protected] |
NotificationAgent |
user123 |
Формат sub-адреса (agent+id@domain) позволяет направлять из одного почтового домена в разные пространства имён и экземпляры агентов.
createSecureReplyEmailResolver
Для сценариев ответа с проверкой подписи. Подтверждает, что входящие письма являются подлинными ответами на ваши исходящие письма, что не позволяет злоумышленникам направлять письма на произвольные экземпляры агента.
import { createSecureReplyEmailResolver } from "agents/email";
const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET);import { createSecureReplyEmailResolver } from "agents/email";
const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET);Когда агент отправляет письмо с replyToEmail() или sendEmail() и secret, он подписывает заголовки маршрутизации временной меткой. Когда приходит ответ, этот resolver проверяет подпись и убеждается, что срок её действия не истёк, прежде чем маршрутизировать его.
Варианты:
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"
},
});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"
},
});Когда использовать: Если ваш агент инициирует переписку по электронной почте и вам нужно, чтобы ответы безопасно маршрутизировались обратно к тому же экземпляру агента.
createCatchAllEmailResolver
Для маршрутизации на один экземпляр. Направляет все письма на конкретный экземпляр агента независимо от адреса получателя.
import { createCatchAllEmailResolver } from "agents/email";
const resolver = createCatchAllEmailResolver("EmailAgent", "default");import { createCatchAllEmailResolver } from "agents/email";
const resolver = createCatchAllEmailResolver("EmailAgent", "default");Когда использовать: Когда у вас есть единственный экземпляр агента, который обрабатывает все письма (например, общий почтовый ящик).
Сочетание резолверов
Вы можете комбинировать несколько 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) => {
// 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");
},
});
},
};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>;Обработка писем в своём Agent
AgentEmail интерфейс
Если у агента onEmail метод вызывается, он получает AgentEmail объект:
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
};Разбор содержимого письма
Используйте библиотеку, например 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);
// Detect auto-reply emails to avoid sending duplicate responses
if (isAutoReplyEmail(parsed.headers)) {
console.log("Skipping auto-reply email");
return;
}
// Process the email...
}
}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...
}
}Это проверяет наличие стандартных заголовков RFC 3834 (Auto-Submitted, X-Auto-Response-Suppress, Precedence) которые указывают на то, что письмо является автоответом.
Ответы на письма
Используйте this.replyToEmail() чтобы отправить ответ через канал ответа входящего письма:
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
});
}
}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
});
}
}Отложенные ответы
replyToEmail() требует активного AgentEmail объект, поэтому он работает только внутри onEmail(). Если вам нужно ответить позже: из запланированной задачи, вызываемого метода или после подтверждения human-in-the-loop, сохраните информацию об отправителе в состоянии и используйте 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 подписывает заголовки маршрутизации агента, чтобы последующие ответы маршрутизировались обратно к этому экземпляру агента через createSecureReplyEmailResolver.
Пересылка писем
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;
}
// Process the email...
}
}class MyAgent extends Agent {
async onEmail(email: AgentEmail) {
if (isSpam(email)) {
email.setReject("Message rejected as spam");
return;
}
// Process the email...
}
}Обработка ошибок
При отправке писем через 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("Sender domain not verified. Verify in dashboard.");
break;
case "E_RATE_LIMIT_EXCEEDED":
console.error("Rate limit exceeded. Back off and retry.");
break;
case "E_DAILY_LIMIT_EXCEEDED":
console.error("Daily sending quota reached.");
break;
case "E_CONTENT_TOO_LARGE":
console.error("Email content exceeds size limit.");
break;
default:
console.error("Email sending failed:", error.message);
}
}
}
}class MyAgent extends Agent {
async onEmail(email: AgentEmail) {
try {
await this.replyToEmail(email, {
fromName: "Support Bot",
body: "Thanks for your email!",
});
} catch (error) {
switch (error.code) {
case "E_SENDER_NOT_VERIFIED":
console.error("Sender domain not verified. Verify in dashboard.");
break;
case "E_RATE_LIMIT_EXCEEDED":
console.error("Rate limit exceeded. Back off and retry.");
break;
case "E_DAILY_LIMIT_EXCEEDED":
console.error("Daily sending quota reached.");
break;
case "E_CONTENT_TOO_LARGE":
console.error("Email content exceeds size limit.");
break;
default:
console.error("Email sending failed:", error.message);
}
}
}
}Типичные коды ошибок
| Код ошибки | Описание | Решение |
|---|---|---|
E_SENDER_NOT_VERIFIED |
Домен или адрес отправителя не проверен | Проверьте в Cloudflare dashboard |
E_RATE_LIMIT_EXCEEDED |
Достигнут лимит скорости отправки | Реализация экспоненциальной задержки |
E_DAILY_LIMIT_EXCEEDED |
Превышена дневная квота | Дождитесь сброса квоты или перейдите на более высокий тарифный план |
E_CONTENT_TOO_LARGE |
Письмо превышает ограничение по размеру | Уменьшите объём вложений или содержимого |
E_RECIPIENT_NOT_ALLOWED |
Получатель не входит в список разрешённых | Проверка разрешённых адресов назначения |
E_RECIPIENT_SUPPRESSED |
Получатель находится в списке подавления | Удалить из списка подавления |
E_VALIDATION_ERROR |
Неверный формат email | Проверка адресов электронной почты |
E_TOO_MANY_RECIPIENTS |
Более 50 получателей | Разбиение на несколько отправок |
Защищённая маршрутизация ответов
Если агент отправляет письма и ожидает ответы, используйте безопасную маршрутизацию ответов, чтобы злоумышленники не могли подделать заголовки и перенаправить письма на произвольные экземпляры агента.
Как это работает
- Исходящие: Когда вы вызываете
replyToEmail()илиsendEmail()сsecret, агент подписывает заголовки маршрутизации (X-Agent-Name,X-Agent-ID) с использованием HMAC-SHA256. - Входящие:
createSecureReplyEmailResolverпроверяет подпись перед маршрутизацией. - Принудительное применение: Если письмо было маршрутизировано через защищённый resolver,
replyToEmail()требует секрета (или явногоnullчтобы отказаться).
Настройка
-
Сохраните подписывающий ключ как секрет Wrangler. Не помещайте его в
varsили зафиксировать её в системе контроля версий:npx wrangler secret put EMAIL_SECRET -
Используйте комбинированный шаблон резолвера:
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>; -
Подписывайте исходящие письма:
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 }); } }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, который отправляет исходящую почту и обрабатывает защищённые ответы:
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-Name и X-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 |
адрес получателя CC, массив адресов или EmailAddress объект(ы) |
bcc |
Адрес получателя BCC, массив адресов или EmailAddress объект(ы) |
inReplyTo |
Message-ID для тредов (задаёт In-Reply-To заголовок) |
headers |
Дополнительные пользовательские заголовки (при конфликте приоритет имеют заголовки агента) |
secret |
Секретный ключ для подписи заголовков маршрутизации агента с помощью HMAC |
routeAgentEmail
function routeAgentEmail<Env>(
email: ForwardableEmailMessage,
env: Env,
options: {
resolver: EmailResolver;
onNoRoute?: (email: ForwardableEmailMessage) => void | Promise<void>;
},
): Promise<void>;Маршрутизирует входящее письмо к подходящему Agent на основе решения резолвера.
| Опция | Описание |
|---|---|
resolver |
Функция, которая определяет, какому агенту направить письмо |
onNoRoute |
Необязательный колбэк, вызываемый, если информация о маршрутизации не найдена. Используйте его, чтобы отклонить письмо или выполнить собственную обработку. Если колбэк не указан, записывается предупреждение, и письмо отбрасывается. |
createSecureReplyEmailResolver
function createSecureReplyEmailResolver(
secret: string,
options?: {
maxAge?: number;
onInvalidSignature?: (
email: ForwardableEmailMessage,
reason: SignatureFailureReason,
) => void;
},
): EmailResolver;
type SignatureFailureReason =
| "missing_headers"
| "expired"
| "invalid"
| "malformed_timestamp";Создаёт резолвер для маршрутизации ответов по электронной почте с проверкой подписи.
| Опция | Описание |
|---|---|
secret |
Секретный ключ для проверки HMAC (должен совпадать с ключом, использованным для подписи) |
maxAge |
Максимальный возраст подписи в секундах (по умолчанию: 30 дней / 2592000 секунд) |
onInvalidSignature |
Необязательный колбэк для логирования при сбое проверки подписи |
signAgentHeaders
function signAgentHeaders(
secret: string,
agentName: string,
agentId: string,
): Promise<Record<string, string>>;Вручную подписать заголовки маршрутизации агента. Возвращает объект с X-Agent-Name, X-Agent-ID, X-Agent-Sig, а также X-Agent-Sig-Ts заголовки.
Полезно при отправке писем через внешние сервисы с сохранением безопасной маршрутизации ответов. Подпись включает временную метку и по умолчанию действительна в течение 30 дней.