Notifications
Rungs never sends anything itself. It records domain events in an outbox, in the same
transaction as each change, and delivers them to your onEvent handler.
async function notify(event: DomainEvent): Promise<void> { // The same event can arrive twice; requestId and seq identify it. const key = `${event.requestId}:${event.seq}`; switch (event.type) { case "task.assigned": await mailer.send({ key, to: users[event.data.assigneeId] ?? "", subject: "To approve" }); break; case "task.reminder": await mailer.send({ key, to: users[event.data.assigneeId] ?? "", subject: "Reminder" }); break; case "request.approved": case "request.rejected": { const request = await worker.get(event.requestId); const to = users[request?.requesterId ?? ""] ?? ""; await mailer.send({ key, to, subject: `Your request was ${event.type.split(".")[1]}` }); break; } }}Runs in CI:examples/docs-snippets/src/guides/notifications.ts
Where to deliver
Section titled “Where to deliver”With onEvent, Rungs delivers right after each command. You can also leave delivery to a
separate worker, so that a slow mail server never slows down a request:
const options = { store, clock: () => new Date(), ids: () => crypto.randomUUID() };
// In the web process: no onEvent. Events wait in the outbox, in the same transaction.const web = createRungs(options);
// In a worker process: delivers them, retrying failures with backoff.const worker = createRungs({ ...options, onEvent: notify });await worker.dispatchPending();Runs in CI:examples/docs-snippets/src/guides/notifications.ts
Delivery is at least once and in order per request. A failing event is retried with backoff
(dispatchRetry, default from 30 seconds up to an hour) and holds back only the later events of
the same request. onEventError tells you about each failure. If nothing ever consumes events,
pass events: "discard" so the outbox does not grow.
Delivered events stay in the PostgreSQL outbox until you remove them with
purgeDispatchedEvents; see Data retention.