internkim

Notifications

What pushes to whom, the two switches a person holds, and the one key pair a deployment sets once.

A member turns notifications on for one device, and the plane pushes to it. There is no notification inbox. The thing a notification is about stays where it always was, so a missed push costs only the interruption. The messenger's conversation list counts what each conversation has unread since the member last read to its end; that read position is kept on Buzz with the member's own key, so their other devices and the Buzz apps agree on it.

What sends what

CategorySent whenReaches
messagethe relay sees a message addressed to someonethose addressed
attendancea member clocks in or outeveryone else in the company
leavea member asks for leavethe administrators, minus the asker
taska task moves between statusesthe requester and the participants, minus whoever moved it
calendaran event's reminder lead time is reachedthe members on that event
approvalan agent run needs a personthe requester
mailnew mail arrivesthe account's owner

The first five come from the central plane and need nothing else installed. approval and mail come from the company's computer, because the agent's runs and the mailbox are things only it can see. Every category is on by default except mail.

Two switches decide whether a push is sent, and both belong to the person: the category switch in their settings, and whether they muted that conversation. The first message in a conversation gives everyone in it an unmuted row in notification, and muting flips that row. A member with no device subscribed is never reached at all, which is the state everyone starts in.

The key pair

Web push is signed, so the plane holds a VAPID pair in its vault. Replacing the pair silently unsubscribes every device that holds the old one, so it is set once. Founding a company sets it: POST /api/company calls the setup-vapid edge function with the founder's token and reports the outcome as notifications. An administrator can make the same call by hand for a deployment founded before that existed.

setup-vapid never replaces a standing pair and answers {"stored":false} when one is kept. When the vault holds no pair but web-push devices are already subscribed, it refuses with 409, since a fresh pair would sign every send with a key no device accepts. A deployment in that state adopts the pair its devices already carry with public.vapid_keys_keep, called with the service key. The edge functions that send answer 503 while any of the three values is missing.

Reminders run on a schedule

Every other notification is sent by the request that caused it. A calendar reminder has no such request, so pg_cron runs announce_event_reminders() every minute and posts the events that are due to the announce-event-reminder edge function. An event with no lead time reminds nobody. The hourly question about shifts nobody closed runs the same way.

Both read an address and a secret from the vault. The migration that creates the jobs also creates their secret, scheduled_job_secret, and the receiving side checks it with is_a_scheduled_job(), which only the service role may call. The secret belongs to the plane and to no company. The addresses are stored by the founding call. With no address stored, the schedule sends nothing and raises nothing, so a self-hosted install that is not set up yet stays quiet.

The secret opens those two jobs and nothing else, and it never leaves the database except in their requests, so it is not rotated on a schedule. If it may have leaked, replace it in the SQL editor:

select vault.update_secret(
  (select id from vault.secrets where name = 'scheduled_job_secret'),
  encode(extensions.gen_random_bytes(32), 'hex')
);

Both sides read the vault on every call, so the next call carries the new value and nothing needs redeploying. A request already queued when it changes arrives with the old one and is refused, which can drop that minute's reminders. Run it just after the minute turns.

What the platforms do differently

Safari, any Apple deviceAndroid ChromeDesktop Chrome
Needs installing firston iOS, to the home screennono
Custom notification iconignored, the app icon is usedshownshown
Its own entry in system settingsyeswhen installed as a WebAPKno, it is Chrome's
What the notification is labelledthe installed namethe installed nameChrome, then the origin

WebKit drops the icon a notification carries, so the sender's name leads the body text. An Android phone that shows notifications only when the app is opened has put Chrome to sleep: the push arrived and waited, and battery optimisation for Chrome is what holds it.

On this page