Concepts, value, and typical clone scenarios — less code.
Concepts, value, and typical clone scenarios — less code.
Preparing Ring content
Preparing Ring content
Preparing Ring content
Ring tells members about new opportunities, messages, entity invitations, wallet events, calls and peer-game challenges. Every notification is stored once and delivered over the channels the member allows: the in-app inbox, push, email or SMS. Members control those channels in their preferences, including a quiet-hours window that now actually silences push and SMS at night.
Use the Founder / Developer tabs in the docs sidebar to filter this page. Push setup (Firebase certificate, VAPID, iOS Home Screen) lives on Push Notifications (FCM).
Status (2026-10-26): Quiet hours are enforced on the server. Mark all as read is a single SQL update and no longer times out on large inboxes. The open notifications page applies live inbox events in place instead of showing stale rows.
| Before | Now |
|---|---|
| Quiet hours were saved in preferences but pushes still arrived | PUSH and SMS are dropped inside the window unless the notification is urgent |
| Mark all as read fetched up to 1000 rows and updated them one by one | One bulk UPDATE marks every unread row and returns the count |
| After mark all as read the list kept showing old unread items | The feed reloads after success and live read_all events clear it |
| Long-poll clients aborted every empty poll after 15 seconds | Long-poll timeout is 30 seconds, above the server's 20 second hold |
Notifications are how members come back. Too few and opportunities go unanswered; too many at 3 AM and members turn push off for good. Quiet hours let members keep push on without being woken up, and the urgent override keeps things like an incoming call alert working.
Chrome, Android and iOS Home Screen push, one delivery per device.
Chat messages, calls and game invites raise typed notifications.
The AI Matcher notifies members who fit a new listing.
Deep-dive: per-device FCM and Web Push fan-out.
Same-workflow: REST routes and preference payloads, including quietHours.
Depends-on: live inbox fan-out on notifications channels.
See-also: CALL_INVITE uses the PUSH channel.
Ring tells members about new opportunities, messages, entity invitations, wallet events, calls and peer-game challenges. Every notification is stored once and delivered over the channels the member allows: the in-app inbox, push, email or SMS. Members control those channels in their preferences, including a quiet-hours window that now actually silences push and SMS at night.
Use the Founder / Developer tabs in the docs sidebar to filter this page. Push setup (Firebase certificate, VAPID, iOS Home Screen) lives on Push Notifications (FCM).
Status (2026-10-26): Quiet hours are enforced on the server. Mark all as read is a single SQL update and no longer times out on large inboxes. The open notifications page applies live inbox events in place instead of showing stale rows.
| Before | Now |
|---|---|
| Quiet hours were saved in preferences but pushes still arrived | PUSH and SMS are dropped inside the window unless the notification is urgent |
| Mark all as read fetched up to 1000 rows and updated them one by one | One bulk UPDATE marks every unread row and returns the count |
| After mark all as read the list kept showing old unread items | The feed reloads after success and live read_all events clear it |
| Long-poll clients aborted every empty poll after 15 seconds | Long-poll timeout is 30 seconds, above the server's 20 second hold |
Notifications are how members come back. Too few and opportunities go unanswered; too many at 3 AM and members turn push off for good. Quiet hours let members keep push on without being woken up, and the urgent override keeps things like an incoming call alert working.
Chrome, Android and iOS Home Screen push, one delivery per device.
Chat messages, calls and game invites raise typed notifications.
The AI Matcher notifies members who fit a new listing.
Deep-dive: per-device FCM and Web Push fan-out.
Same-workflow: REST routes and preference payloads, including quietHours.
Depends-on: live inbox fan-out on notifications channels.
See-also: CALL_INVITE uses the PUSH channel.
Ring tells members about new opportunities, messages, entity invitations, wallet events, calls and peer-game challenges. Every notification is stored once and delivered over the channels the member allows: the in-app inbox, push, email or SMS. Members control those channels in their preferences, including a quiet-hours window that now actually silences push and SMS at night.
Use the Founder / Developer tabs in the docs sidebar to filter this page. Push setup (Firebase certificate, VAPID, iOS Home Screen) lives on Push Notifications (FCM).
Status (2026-10-26): Quiet hours are enforced on the server. Mark all as read is a single SQL update and no longer times out on large inboxes. The open notifications page applies live inbox events in place instead of showing stale rows.
| Before | Now |
|---|---|
| Quiet hours were saved in preferences but pushes still arrived | PUSH and SMS are dropped inside the window unless the notification is urgent |
| Mark all as read fetched up to 1000 rows and updated them one by one | One bulk UPDATE marks every unread row and returns the count |
| After mark all as read the list kept showing old unread items | The feed reloads after success and live read_all events clear it |
| Long-poll clients aborted every empty poll after 15 seconds | Long-poll timeout is 30 seconds, above the server's 20 second hold |
Notifications are how members come back. Too few and opportunities go unanswered; too many at 3 AM and members turn push off for good. Quiet hours let members keep push on without being woken up, and the urgent override keeps things like an incoming call alert working.
Chrome, Android and iOS Home Screen push, one delivery per device.
Chat messages, calls and game invites raise typed notifications.
The AI Matcher notifies members who fit a new listing.
Deep-dive: per-device FCM and Web Push fan-out.
Same-workflow: REST routes and preference payloads, including quietHours.
Depends-on: live inbox fan-out on notifications channels.
See-also: CALL_INVITE uses the PUSH channel.
createNotification in features/notifications/services/notification-service.ts loads the user's preferences once, then calls determineChannels(requested, preferences, priority) from features/notifications/lib/determine-channels.ts. Deliveries are built from that list, so a muted PUSH never reaches FCM.
| Step | Rule |
|---|---|
| No preferences row | IN_APP always, plus PUSH when the caller asked for it |
| Stored preferences | Honor channels.push and inApp opt-outs |
| Quiet hours | suppressQuietChannels removes PUSH and SMS when isWithinQuietHours is true and priority is not URGENT. IN_APP and EMAIL stay |
Preferences store quietHours: { enabled, startTime, endTime, timezone } with times in HH:mm.
timezone (fallback UTC).22:00 to 08:00, is an overnight window.NORMAL when the caller omits it. Send URGENT for anything that must interrupt.| Piece | Path | Behavior |
|---|---|---|
| Mark all read HTTP | app/api/notifications/read-all | Calls markAllNotificationsAsRead |
| Bulk update | notification-service.ts | One UPDATE notifications ... WHERE data->>'user_id' = $1 AND data->>'read_at' IS NULL through getSharedPgPool, then publishes the unread count |
| Live patch | features/notifications/lib/apply-inbox-to-feed.ts | Applies notification, read, read_all and delete tunnel payloads to the rows on screen, respecting the active type filter |
| List UI | features/notifications/components/notification-list.tsx | Uses applyInboxToFeed for live events and reloads the cursor feed after mark all read |
The bulk update uses raw SQL on the notifications JSONB table, so it applies to PostgreSQL backends (k8s-postgres-fcm, supabase-fcm).
notifications:inbox and the bell UI. On Vercel-style long polling, lib/tunnel/config.ts sets the client timeout to 30 seconds because /api/tunnel/poll holds empty polls for up to 20 seconds.fcm_tokens) plus RFC Web Push (push_subscriptions). See Push Notifications (FCM).No. Only push and SMS are skipped. The notification is still stored, shown in the inbox and sent by email if email is on.
No. It uses the existing notifications table. Older clones on firebase-full should not rely on this path.
Check that the member saved quiet hours with a valid zone, and whether the sender used URGENT priority, which bypasses the window by design.
See-also: interactive notify that emits typed notifications.
createNotification in features/notifications/services/notification-service.ts loads the user's preferences once, then calls determineChannels(requested, preferences, priority) from features/notifications/lib/determine-channels.ts. Deliveries are built from that list, so a muted PUSH never reaches FCM.
| Step | Rule |
|---|---|
| No preferences row | IN_APP always, plus PUSH when the caller asked for it |
| Stored preferences | Honor channels.push and inApp opt-outs |
| Quiet hours | suppressQuietChannels removes PUSH and SMS when isWithinQuietHours is true and priority is not URGENT. IN_APP and EMAIL stay |
Preferences store quietHours: { enabled, startTime, endTime, timezone } with times in HH:mm.
timezone (fallback UTC).22:00 to 08:00, is an overnight window.NORMAL when the caller omits it. Send URGENT for anything that must interrupt.| Piece | Path | Behavior |
|---|---|---|
| Mark all read HTTP | app/api/notifications/read-all | Calls markAllNotificationsAsRead |
| Bulk update | notification-service.ts | One UPDATE notifications ... WHERE data->>'user_id' = $1 AND data->>'read_at' IS NULL through getSharedPgPool, then publishes the unread count |
| Live patch | features/notifications/lib/apply-inbox-to-feed.ts | Applies notification, read, read_all and delete tunnel payloads to the rows on screen, respecting the active type filter |
| List UI | features/notifications/components/notification-list.tsx | Uses applyInboxToFeed for live events and reloads the cursor feed after mark all read |
The bulk update uses raw SQL on the notifications JSONB table, so it applies to PostgreSQL backends (k8s-postgres-fcm, supabase-fcm).
notifications:inbox and the bell UI. On Vercel-style long polling, lib/tunnel/config.ts sets the client timeout to 30 seconds because /api/tunnel/poll holds empty polls for up to 20 seconds.fcm_tokens) plus RFC Web Push (push_subscriptions). See Push Notifications (FCM).No. Only push and SMS are skipped. The notification is still stored, shown in the inbox and sent by email if email is on.
No. It uses the existing notifications table. Older clones on firebase-full should not rely on this path.
Check that the member saved quiet hours with a valid zone, and whether the sender used URGENT priority, which bypasses the window by design.
See-also: interactive notify that emits typed notifications.
createNotification in features/notifications/services/notification-service.ts loads the user's preferences once, then calls determineChannels(requested, preferences, priority) from features/notifications/lib/determine-channels.ts. Deliveries are built from that list, so a muted PUSH never reaches FCM.
| Step | Rule |
|---|---|
| No preferences row | IN_APP always, plus PUSH when the caller asked for it |
| Stored preferences | Honor channels.push and inApp opt-outs |
| Quiet hours | suppressQuietChannels removes PUSH and SMS when isWithinQuietHours is true and priority is not URGENT. IN_APP and EMAIL stay |
Preferences store quietHours: { enabled, startTime, endTime, timezone } with times in HH:mm.
timezone (fallback UTC).22:00 to 08:00, is an overnight window.NORMAL when the caller omits it. Send URGENT for anything that must interrupt.| Piece | Path | Behavior |
|---|---|---|
| Mark all read HTTP | app/api/notifications/read-all | Calls markAllNotificationsAsRead |
| Bulk update | notification-service.ts | One UPDATE notifications ... WHERE data->>'user_id' = $1 AND data->>'read_at' IS NULL through getSharedPgPool, then publishes the unread count |
| Live patch | features/notifications/lib/apply-inbox-to-feed.ts | Applies notification, read, read_all and delete tunnel payloads to the rows on screen, respecting the active type filter |
| List UI | features/notifications/components/notification-list.tsx | Uses applyInboxToFeed for live events and reloads the cursor feed after mark all read |
The bulk update uses raw SQL on the notifications JSONB table, so it applies to PostgreSQL backends (k8s-postgres-fcm, supabase-fcm).
notifications:inbox and the bell UI. On Vercel-style long polling, lib/tunnel/config.ts sets the client timeout to 30 seconds because /api/tunnel/poll holds empty polls for up to 20 seconds.fcm_tokens) plus RFC Web Push (push_subscriptions). See Push Notifications (FCM).No. Only push and SMS are skipped. The notification is still stored, shown in the inbox and sent by email if email is on.
No. It uses the existing notifications table. Older clones on firebase-full should not rely on this path.
Check that the member saved quiet hours with a valid zone, and whether the sender used URGENT priority, which bypasses the window by design.
See-also: interactive notify that emits typed notifications.