Skip to content

Sync recipient records

Synchronize application recipient records with Email Sending lifecycle events.

Use Email Sending event subscriptions to update application records after delivery problems. This example uses Cloudflare Queues and Workers KV to remove recipients from transactional notifications.

Prepare the resources

Before you begin:

Store each eligible recipient address as a key in KV. The value can contain notification preferences or related metadata.

Review the event flow

  1. Email Sending publishes bounce and complaint events.
  2. A queue delivers those events to a Worker.
  3. The Worker removes ineligible recipient records from KV.

Choose removal events

Remove records for every message.complained event. These events indicate that a recipient reported the message as spam.

Remove bounced records only when payload.bounce.type is "hard". Temporary failures produce message.deferred events while retries remain. Exhausted temporary retries can produce message.bounced events with a "soft" bounce type.

For payload details, refer to Available Email Sending events.

Create the queue and subscription

Create a queue and subscribe it to your sending domain:

  1. In the Cloudflare dashboard, go to the Queues page. Create a queue named email-events.

    Go to Queues
  2. Select email-events, then select Subscriptions > Subscribe to events.

  3. Enter a subscription name and select Email Sending as the source.

  4. Select your sending domain and the message.bounced and message.complained events.

  5. Select Subscribe.

Configure the Worker

Bind the KV namespace and register the Worker as the queue consumer:

JSONC
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "recipient-record-sync",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-07-15",
"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"
}
]
}
}

The configuration creates email-events-dlq during deployment. Queues moves events there after three retries.

Add the queue consumer

The queue() handler processes each event independently. It deletes applicable recipient records and retries failed KV operations.

src/index.js
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,
});
}

Deleting a missing KV key succeeds. This makes repeated event delivery safe.

The handler acknowledges each successful message. Failed operations move to the dead letter queue after three retries.

Deploy the Worker

Deploy the Worker and its queue consumer configuration:

npx wrangler deploy

Monitor the dead letter queue for failed events. Reprocess them after fixing the underlying error.