# Personal notifications

Bucker has two kinds of notification:

- **Team alerts.** Alert rules page a channel when telemetry crosses a line. They are
  configured under **Alerts** and are described in [alert-rules.md](alert-rules.md).
  Nothing on this page changes them.
- **Personal notifications.** These are about things that happen to you: an approval
  you can decide, an issue assigned to you, an issue you follow regressing. You
  configure them under **Settings › Notifications**.

## What reaches you, and how

Personal notifications use one short, closed list of event types. The inbox is built
from the same list.

| Type | When | Inbox | Email | Slack DM |
| --- | --- | --- | --- | --- |
| `approval_waiting` | An approval you can decide is waiting | on | on | on |
| `assigned` | Someone assigned an issue to you | on | on | on |
| `mention` | Someone mentioned you | on | on | on |
| `watched_regressed` | An issue you participate in regressed | on | on | off |
| `watched_resolved` | Someone else resolved an issue you participate in | on | off | off |
| `remediation_finished` | A remediation you started finished | on | off | off |
| `billing_usage` | Usage, spend and spike notices (owners and billing members) | — | on | — |
| `security_audit` | A security event your organization opted to alert owners on (owners only) | — | on | off |
| `deploy_shipped` | A release carrying commits you wrote was deployed | on | off | off |

The table shows the defaults. Each cell can be turned on or off.

```
GET /me/notifications
PUT /me/notifications   { "changes": [{ "type": "assigned", "channel": "EMAIL", "enabled": false }] }
PUT /me/notifications   { "approvalsBypassQuietHours": true }
```

These are account settings, not org settings. They apply in every organization you
belong to. `billing_usage` is the usage-notice switch that predates the matrix.
`GET/PUT /me/notifications/usage` still work, and they read and write the same cell.

A Slack DM can reach you only after you link your Slack account by running
`/bucker link` in Slack.

`security_audit` is opt-in per organization. An owner chooses which audit actions alert
(`auditAlertActions` on `PATCH /orgs/:orgSlug/security-policy`, from a closed list:
break-glass taken, support access changed, the security policy changed, the allowlist
cleared by an operator, a SCIM or org CI token created, SSO configured or removed, a
member's role changed, a custom role edited, an audit sink removed). The worker reads the
audit log after the fact, so an alert can never fail or slow the action it reports, and
the owner who caused an event is not told about it.

`deploy_shipped` comes from the release's commit list (the commits a release carries,
from its connected repository). A commit is yours when its author email is your account's
email, or when it is GitHub's private form (`<id>+<login>@users.noreply.github.com`) for
the GitHub account you connected under **Connected sign-in accounts**. You are told once
per deploy, however many of the commits are yours, and only by organizations you belong
to. A deploy reported more than a day after it happened is not announced.

## The inbox

The bell in the dashboard header shows how many unread things are waiting on you in
the current organization. It opens your inbox. **Open the inbox page** goes to
`/org/{org}/inbox`, the same list on a full page. The inbox groups rows by what they
need from you:

| Section | Holds |
| --- | --- |
| Waiting on you | Approvals you can decide, while they are still undecided |
| Assigned to you | Issues assigned to you |
| Mentions | Comments that mention you |
| Issues you follow | Regressions and resolutions of issues you participate in |
| Finished | Fix runs you started that have finished, and deploys that shipped your commits |

- Each row links to the issue or approval it is about. It never holds a copy of the
  conversation.
- Opening a row marks it read. Read state is stored on the server, so it follows you
  to other browsers and devices.
- An approval leaves **Waiting on you** as soon as anyone decides it.

```
GET  /me/inbox?orgSlug={org}
GET  /me/inbox/count?orgSlug={org}
POST /me/inbox/read   { "orgSlug": "{org}", "ids": ["…"] }   or   { "orgSlug": "{org}", "all": true }
```

The inbox is per person and per organization. Turning off a type's **Inbox** cell in
the matrix keeps that type out of the inbox. Its email and DM still follow their own
cells.

## Issues you participate in

You participate in an issue if any of these is true:

- It is assigned to you. This is read live, so reassigning it moves the notifications too.
- You resolved it.
- You watch it. Use the **Watch** button on the issue page, or call
  `PUT /orgs/{org}/projects/{project}/issues/{issue}/watch { "watching": true }`.
- You commented on it. This one arrives with issue comments.

**Unwatch** stops all of these for that one issue, even when you are its assignee.

Participants get `watched_regressed` when the issue regresses, and `watched_resolved`
when someone else resolves it. The person who caused an event is never notified about
it. For example, someone who assigns an issue to themselves gets no "assigned to you".

Bucker builds these notifications from the records the product already keeps: the
issue's activity, fix runs and approval requests. They arrive however the change was
made, whether from the dashboard, Slack, the API, Jira or the ingest pipeline. Each one
is written once, even if a pass repeats. The sweep runs every minute in the worker. It
appears as `personal-notifications` in the operator console, where it can be paused.

## The weekly digest

Under **Settings › Notifications › Digest** you can choose **Weekly, on Mondays**
(`PUT /me/notifications { "digestCadence": "weekly" }`). You then drop out of the
daily digest and get one email each Monday, per organization you own or administer. It
covers the previous seven days and uses the daily digest's format. Quiet hours delay it
on Monday the same way they delay the daily digest.

## Quiet hours

You set one quiet window, under the digest settings (`PUT /me/digest`). It applies to
the digest and to every personal email and Slack DM:

- A notification that falls inside the window is **held**, not dropped. It is sent
  when the window ends.
- The inbox is never held.
- **Approvals bypass quiet hours** is the one exception, and it is off by default. When
  it is on, approval requests reach you at any hour.
- Team alert rules ignore personal quiet hours. A team decides who it pages.

## Where this is defined

- The vocabulary and defaults: `code/domain/src/modules/notifications/vocabulary.ts`.
- The matrix and the delivery plan: `code/domain/src/modules/notifications/preferences.ts`.
- Participants and the watch: `code/domain/src/modules/notifications/participants.ts`.
- The sources, the fan-out and delivery: `sources.ts`, `fanout.ts` and `delivery.ts` in
  the same directory. The sweep's schedule is `code/workers/src/personal-notifications/`.
- The inbox: `code/domain/src/modules/notifications/inbox.ts`; the bell and the list:
  `code/frontend/src/components/shell/inbox-bell.tsx`, `code/frontend/src/components/inbox/`.
- The hooks for issue comments: `recordCommenter` in `participants.ts` and `notifyMention`
  in `fanout.ts`.
