Channels and preferences Required
Send one message per event
Deliver each event to a person on one channel first, and move to the next channel only if they have not seen it. Never send the same news on every channel at once.
How to do it
Give each event and recipient a key, such as order-1042:shipped:user-77, with a unique constraint, as in send each message once. Order channels from least to most intrusive, such as in-app, then push, then email. Schedule the next channel after a wait, such as 15 minutes, and cancel it when the in-app item is read. Only critical types may use several channels at once.
Example
const ESCALATION_WAIT_MS = 15 * 60 * 1000;
export async function deliver(event, userId) {
const key = `${event.id}:${userId}`;
const [first, ...rest] = await channelsFor(userId, event.type);
if (!(await db.deliveries.insertIfNew({ key, channel: first }))) return;
rest.forEach((channel, step) =>
queue.add('escalate', { key, channel }, { delay: ESCALATION_WAIT_MS * (step + 1) }));
}
// The escalate worker skips the job when the in-app item for this key is already read.
from datetime import timedelta
ESCALATION_WAIT = timedelta(minutes=15)
def deliver(event, user_id):
key = f"{event.id}:{user_id}"
first, *rest = channels_for(user_id, event.type)
if not db.deliveries.insert_if_new(key=key, channel=first):
return
for step, channel in enumerate(rest, start=1):
queue.enqueue_in(ESCALATION_WAIT * step, escalate, key, channel)
How to check
Trigger a shipment and read it in the app within the wait. One in-app item exists, and no push or email is sent. Trigger the same event twice and the count stays at one.
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 notifications playbook: https://agentwranglers.se/playbook/notifications/channels-and-preferences/one-message-per-event.