Skip to main content

User Notifications System

Approvedv1.2Owner: @product-team

TL;DR

Unified real-time notification system that replaces email-only alerts with in-app, push, and email delivery across all BeWith touchpoints.


Background

Users currently receive critical updates only via email, leading to delayed responses and missed actions. Support tickets related to "missed updates" account for ~18% of monthly volume.

Problem Statement

Users are missing time-sensitive events because there is no real-time, in-app notification mechanism.

Affected personas:

  • End users who need to act on requests
  • Admins who monitor platform health
  • Integration partners expecting webhook reliability

Goals

  • Real-time in-app notifications (< 500ms delivery P95)
  • Push notifications for mobile
  • User-controlled notification preferences
  • Notification history (30-day retention)
  • Batch digest emails for low-priority events

Non-Goals

  • SMS notifications (deferred to v2)
  • Third-party notification aggregators (Slack, Teams) — tracked separately in a follow-up RFC
  • Marketing/promotional notifications

Success Metrics

MetricBaselineTarget
Support tickets re: missed updates18% of volume< 5%
Notification open rate (in-app)≥ 70%
Delivery P95 latency< 500ms
User preference adoption≥ 60% users configure prefs within 30 days

User Stories

As an end user,
I want to see a real-time badge in the navbar when I have unread notifications
so that I don't have to check my email to know something needs my attention.

As an admin,
I want to receive a push notification when a critical system alert fires
so that I can respond immediately regardless of whether I have the app open.

As any user,
I want to control which notification types I receive and via which channel
so that I'm not overwhelmed by noise.

Architecture

High-level flow

Notification preference model

Solution Design

Data model

CREATE TABLE notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
type TEXT NOT NULL, -- 'request_approved', 'system_alert', …
priority TEXT NOT NULL DEFAULT 'medium', -- 'high' | 'medium' | 'low'
title TEXT NOT NULL,
body TEXT,
data JSONB, -- arbitrary payload
read_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX notifications_user_unread
ON notifications(user_id, created_at DESC)
WHERE read_at IS NULL;

API

MethodEndpointDescription
GET/api/v1/notificationsPaginated notification list
PATCH/api/v1/notifications/:id/readMark single as read
POST/api/v1/notifications/read-allMark all as read
GET/api/v1/notifications/preferencesGet user prefs
PUT/api/v1/notifications/preferencesUpdate user prefs

Edge Cases

CaseHandling
User offline when notification firesPersist; deliver via push; show on next app load
WebSocket disconnects mid-deliveryClient reconnects and fetches unread on mount
User disables all channelsRecord persisted; available in history
Notification storm (> 50 events/min for one user)Batch into digest, rate-limit push

Open Questions

  • @eng-lead — Should WebSocket connections be managed by the API Gateway or a dedicated WS service?
  • @product-team — What's the retention policy for read notifications? 30 days proposed.
  • @design — Notification panel UX — flyout vs dedicated page?

Alternatives Considered

OptionProsConsDecision
PollingSimpleBattery drain, latency❌ Rejected
Server-Sent EventsSimpler than WSOne-directional❌ Rejected
WebSocketsBidirectional, low latencyConnection management✅ Chosen
Third-party (Knock, Novu)Quick to shipVendor lock-in, costDeferred to v2 evaluation

Milestones


Last updated by @product-team on 2026-05-20