Automation and agents Required
Send each message once
Give every automated message a deduplication key, and send through a queue that skips a key it has already sent.
How to do it
Retries, double clicks and crashed workers all send the same receipt twice. Build the key from the event, the template and the recipient, such as order-1042:shipped:ada@example.org. Store it with a unique constraint when the message is queued. Queue the message in the same database transaction as the change that causes it, as the outbox rule describes. Pass the key on as an idempotency key when your sending service accepts one.
Example
const dedupeKey = `order-${order.id}:shipped:${order.email}`;
await db.query(
'INSERT INTO outbox (dedupe_key, template, recipient) VALUES ($1, $2, $3) ON CONFLICT (dedupe_key) DO NOTHING',
[dedupeKey, 'order-shipped', order.email],
);
dedupe_key = f"order-{order.id}:shipped:{order.email}"
db.execute(
"INSERT INTO outbox (dedupe_key, template, recipient) VALUES (%s, %s, %s) ON CONFLICT (dedupe_key) DO NOTHING",
(dedupe_key, "order-shipped", order.email),
)
How to check
Trigger the same order event twice, and kill the worker once in the middle of a send. The test recipient gets exactly one message.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers email playbook: https://agentwranglers.se/playbook/email/automation-and-agents/send-once.