← Cloudflare Email Service / email-service / examples / email-sending
Синхронизация записей получателей
Используйте Подписки на события Email Sending для обновления записей приложения после проблем с доставкой. В этом примере используется Cloudflare Queues и Workers KV для исключения получателей из транзакционных уведомлений.
Подготовьте ресурсы
Перед началом работы:
- Включите Домен Email Sending.
- Создайте Проект Worker.
- Создайте Пространство имён Workers KV.
Сохраняйте адрес каждого подходящего получателя в качестве ключа в KV. В значении можно указать настройки уведомлений или другие связанные метаданные.
Изучите последовательность событий
- Email Sending публикует события возврата и жалоб.
- Очередь доставляет эти события в Worker.
- Worker удаляет из KV записи о получателях, которые больше не соответствуют критериям.
Выберите события удаления
Удалите записи для каждого message.complained событие. Такие события означают, что получатель пометил сообщение как спам.
Удаляйте записи об отказах доставки только если payload.bounce.type это "hard". Временные сбои приводят к message.deferred события, пока остаются попытки повтора. Исчерпанные временные попытки повтора могут приводить к message.bounced события с "soft" тип возврата.
Подробнее о теле запроса см. в Доступные события Email Sending.
Создайте очередь и подписку
Создайте очередь и подпишите её на домен отправки:
-
На панели управления Cloudflare перейдите к разделу Queues страница. Создайте очередь с именем
Перейдите в Queues ↗email-events. -
Выберите
email-events, затем выберите Подписки > Подписка на события. -
Введите имя подписки и выберите Email Sending в качестве источника.
-
Выберите домен отправки и
message.bouncedиmessage.complainedсобытия. -
Выберите Подписаться.
Настройка Worker
Привяжите пространство имен KV и зарегистрируйте Worker как потребителя очереди:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "recipient-record-sync",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-08-28",
"kv_namespaces": [
{
"binding": "RECIPIENTS",
"id": "<RECIPIENTS_KV_NAMESPACE_ID>"
}
],
"queues": {
"consumers": [
{
"queue": "email-events",
"max_batch_size": 10,
"max_retries": 3,
"dead_letter_queue": "email-events-dlq"
}
]
}
}name = "recipient-record-sync"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-08-28"
[[kv_namespaces]]
binding = "RECIPIENTS"
id = "<RECIPIENTS_KV_NAMESPACE_ID>"
[[queues.consumers]]
queue = "email-events"
max_batch_size = 10
max_retries = 3
dead_letter_queue = "email-events-dlq"Эта конфигурация создаёт email-events-dlq во время развёртывания. Queues перемещает туда события после трёх попыток.
Добавьте потребителя очереди
queue() обработчик обрабатывает каждое событие независимо. Он удаляет соответствующие записи получателей и повторяет неудачные операции KV.
export default {
async queue(batch, env) {
for (const message of batch.messages) {
try {
const event = message.body;
if (shouldRemove(event)) {
await removeRecipient(env, event);
}
message.ack();
} catch (error) {
console.error("Failed to process Email Sending event", {
eventId: message.body.payload.eventId,
error,
});
message.retry();
}
}
},
};
function shouldRemove(event) {
if (event.type === "cf.email.sending.message.complained") {
return true;
}
return (
event.type === "cf.email.sending.message.bounced" &&
event.payload.bounce?.type === "hard"
);
}
async function removeRecipient(env, event) {
await env.RECIPIENTS.delete(event.payload.recipient);
console.log("Removed recipient record", {
eventId: event.payload.eventId,
reason: event.type,
});
}interface Env {
RECIPIENTS: KVNamespace;
}
interface EmailSendingEvent {
type:
| "cf.email.sending.message.bounced"
| "cf.email.sending.message.complained";
payload: {
eventId: string;
recipient: string;
bounce?: {
type: "hard" | "soft";
};
};
}
export default {
async queue(batch, env): Promise<void> {
for (const message of batch.messages) {
try {
const event = message.body;
if (shouldRemove(event)) {
await removeRecipient(env, event);
}
message.ack();
} catch (error) {
console.error("Failed to process Email Sending event", {
eventId: message.body.payload.eventId,
error,
});
message.retry();
}
}
},
} satisfies ExportedHandler<Env, EmailSendingEvent>;
function shouldRemove(event: EmailSendingEvent): boolean {
if (event.type === "cf.email.sending.message.complained") {
return true;
}
return (
event.type === "cf.email.sending.message.bounced" &&
event.payload.bounce?.type === "hard"
);
}
async function removeRecipient(
env: Env,
event: EmailSendingEvent,
): Promise<void> {
await env.RECIPIENTS.delete(event.payload.recipient);
console.log("Removed recipient record", {
eventId: event.payload.eventId,
reason: event.type,
});
}Удаление отсутствующего ключа KV завершается успешно. Это обеспечивает безопасность повторной доставки событий.
Обработчик подтверждает получение каждого успешного сообщения. Операции с ошибкой перемещаются в очередь недоставленных сообщений после трёх попыток.
Разверните Worker
Разверните Worker и конфигурацию его потребителя очереди:
npx wrangler deployОтслеживайте очередь недоставленных сообщений на предмет несработавших событий. Обрабатывайте их повторно после устранения первопричины ошибки.
Дополнительные ресурсы
- Подписки на события : ознакомьтесь со схемами событий.
- Списки подавления : узнайте больше об автоматическом подавлении.
- Помещает повторные попытки в очередь : управление повторными попытками отправки сообщений.
- Согласованность Workers KV : учитывайте задержки распространения.