| 1 | # 0029 — Repository events go through an outbox, and notifications are its first consumer |
| 2 | |
| 3 | **Status:** adopted |
| 4 | |
| 5 | ## Decision |
| 6 | |
| 7 | A change that someone may want to hear about writes an `outbox_event` row with `publishEvent`, in the same transaction as the change. The row names its type ( |
| 8 | |
| 9 | `OutboxService` polls for unprocessed rows every two seconds, claims a batch with `FOR UPDATE SKIP LOCKED` so several API instances can share the work, and hands each event to its consumers. A failing event is retried on the next poll and set aside with its last error after five attempts. |
| 10 | |
| 11 | The only consumer today is `NotifierService`. For each event it works out who to tell and why: |
| 12 | |
| 13 | - assigned: the person assigned; |
| 14 | - mentioned: `@user` in the text the event carries; |
| 15 | - team_mentioned: `@org/team` for the repository's own organization, written by one of its members; |
| 16 | - author and subscribed: everyone subscribed to the thread; |
| 17 | - watching: everyone watching the repository at `all`. |
| 18 | |
| 19 | Nobody hears about their own activity, about a repository they ignore, or about one they cannot read. Opening, commenting, reviewing, being mentioned and being assigned subscribe the person to the thread; an explicit unsubscribe outlasts that, though a mention or an assignment still notifies. Each person has one `notification` row per thread, bumped back to unread by later activity, and an email rendered from one of the `thread-*` |
| 20 | |
| 21 | ## Why |
| 22 | |
| 23 | Writing the event in the change's transaction means an event exists exactly when its change does: no notification for a comment that rolled back, and none lost to a crash between the commit and the send. Consumers read the rows when they run, not a snapshot, so the payload stays small and an event whose comment was deleted in the meantime is skipped. |
| 24 | |
| 25 | Webhooks need the same events with the same guarantees. They become a second call in `OutboxService.dispatch` that writes one delivery row per subscribed hook, with its own retries and log; nothing that publishes events changes. |
| 26 | |
| 27 | ## Consequences |
| 28 | |
| 29 | - Delivery is at least once. A consumer must tolerate running twice: notification rows are upserts, but an email can repeat if a batch fails after sending. |
| 30 | - A batch holds its row locks while consumers run, emails included. |
| 31 | - Editing an issue or comment to add a mention does not notify yet. |
| 32 | - There is no per-user email preference yet; unsubscribing from a thread or ignoring a repository is the control. |
| 33 | - Only verified addresses get email, so an instance with email verification off sends none; the inbox still fills. |
| 34 | - Processed events are kept; nothing prunes the table yet. |