Skip to content

PRD: Activity notifications & realtime push

ModulePlatform (CORE-16)PRD IDPRD-ACT-001
StatusShippedOwnerPhát Nguyễn
Date2026-06-15Versionv1.0
CapabilityNotifications · realtime deliveryURDACT · WSS

TL;DR

A noteworthy domain activity becomes a notification that reaches exactly the right people, instantly: the platform fans one activity out to its audience - a whole organizer, a whole merchant, or an explicit user list - persists one record per recipient, and pushes each over an authenticated, end-to-end-encrypted real-time channel. Users read and clear only their own; a dropped channel never loses a record, and operators get a thin set of controls to inspect and steer live connections.

1. Context & Problem

BANA produces a constant stream of noteworthy moments - a payment clears, an order completes - but each stays buried in the service that produced it. There has been no platform-wide way to turn an activity into a durable, per-person notification and surface it live the instant it happens; staff had to refresh and re-query to learn anything had changed.

What was missing is a backbone that decouples producing an activity from delivering it: an event seam any service can publish to, a worker that fans one activity out to the right recipients in the right tenant scope, a durable record that survives a disconnect, and a live transport that pushes each notification to its owner - and only its owner.

This increment ships that backbone as a first-class platform capability: the activity-notification pipeline (ACT) and the realtime stream (WSS) it rides, plus a self-scoped read API and a stable, encrypted delivery contract.

2. Goals & Non-Goals

Goals

  • An event-driven pipeline: consume an activity → resolve recipients → render content → persist one record each → push live.
  • Scoped recipient resolution - fan one activity to an organizer, a merchant, or an explicit user list, falling back to the actor when no audience resolves.
  • A durable, self-scoped read API - each user lists, counts (incl. unread), and marks one or all read, for their own notifications only.
  • A live, authenticated and end-to-end-encrypted push to each recipient's private channel, on a fixed channel/topic contract.
  • Resilience - persistence always succeeds; a missing or failing channel never fails the pipeline or blocks other recipients.
  • Thin administrative connection controls - status, client listing, targeted send, broadcast, disconnect - behind permissions.

Non-Goals

  • Email / SMS / push-notification channels - this increment is in-app realtime only.
  • A notification-preferences or per-user mute/subscription model.
  • The producers' own logic - publishing the activity is the producer's job; this capability consumes it.
  • Cross-tenant channel authorization hardening on the transport (a known follow-up, see §11).
  • Bell / activity-feed UI styling - this PRD specifies the API and delivery contract the client renders.

3. Success Metrics

MetricTarget / signal
Delivery latencyActivity event → recipient's live push in near real time (sub-second under normal load)
Recipient accuracyA notification reaches exactly the users in the activity's scope; no cross-tenant leakage
DurabilityEvery recipient has a persisted record even if their connection is down; read-state survives reconnect
Self-scopingA user can only ever read or mutate their own notifications
ResilienceA realtime outage or a single failed push degrades to persistence-only, with zero pipeline failures

4. Personas & Use Cases

PersonaGoal in this feature
Owner / ManagerSee organizer or merchant activity surface live, without refreshing
Staff / CashierReceive notifications addressed to them and clear them as read
Connected client (web / POS)Hold a live connection and reflect new notifications + read-state instantly
Platform operatorInspect live connections and steer or cut a session when needed

Core scenario: a payment succeeds, and the paying service publishes a successful-payment activity carrying the actor and the order. The worker resolves the recipients for that scope, renders a human-readable message, and writes one notification each. Recipients holding a live connection see it appear instantly on their private channel; an offline recipient finds it waiting on next load. Anyone marks it read - or clears everything at once - and the read-state holds across sessions.

5. User Stories

  • As an owner, I want activity to appear the instant it happens, so I can watch the business without refreshing.
  • As staff, I want only the notifications addressed to me, so my feed is relevant and never shows another person's activity.
  • As a connected client, I want notifications over a live, encrypted channel, so updates are immediate and private in transit.
  • As a user, I want to count my unread notifications and mark them read - one or all, so my feed reflects what I have seen.
  • As the platform, I want a notification to survive a dropped connection, so an offline user never misses it.
  • As an operator, I want to see and steer live connections, so I can diagnose or cut a session.

6. Functional Requirements

#RequirementURD ref
FR-1An activity event on the platform stream is consumed and turned into one notification per resolved recipientURD-ACT-001
FR-2Recipients are resolved by scope - whole organizer, whole merchant, or an explicit user list - falling back to the actor when none resolveURD-ACT-002
FR-3Each notification stores its recipient, type, organizer, rendered plain-text + rich-text content, an optional action link, structured data (incl. the actor), and a read flag with timestampURD-ACT-003
FR-4Content is rendered from the event type into a human-readable message (one rendered plain-text + rich-text string per notification, the same for every recipient); only recognized event types produce a notification, unknown types are skipped without error. Today the single live type renders a fixed Vietnamese string - per-user-language localization is not yet implementedURD-ACT-004 · URD-ACT-005
FR-5A signed-in user lists their own notifications (paginated / filterable) with total and unread countsURD-ACT-006
FR-6A user can count their notifications (e.g. unread) and mark one read or all read, each carrying a read timestampURD-ACT-007 · URD-ACT-008
FR-7Every read and write is scoped to the authenticated recipient - a user never sees or mutates another's notificationsURD-ACT-009
FR-8On creation, each notification is pushed live to its recipient's private channel over the realtime transportURD-WSS-001
FR-9Clients connect over an authenticated channel and complete an encrypted key-exchange handshake; post-handshake payloads are end-to-end encryptedURD-WSS-002
FR-10Delivery uses a fixed channel/topic contract and fans out across server instances; a missing channel or a single failed push degrades to persistence-only without blocking othersURD-WSS-003 · URD-WSS-004 · URD-WSS-005
FR-11Administrative connection controls - status, list clients, get client, broadcast, send-to-channel, send-to-client, disconnect - are available behind permissionsURD-WSS-006

Full requirement text and acceptance criteria live in the Platform URD - ACT and WSS. This PRD references them rather than restating them.

7. Non-Functional Requirements

AreaRequirement
Tenancy & isolationRecipient resolution is scoped per organizer / merchant; a user's read API is confined to their own account - no cross-tenant or cross-user leakage
DurabilityEvery push is backed by a persisted record; read-state is retrievable after reconnect
Decoupling & scaleThe event seam decouples producers from fan-out; a worker absorbs bursts without blocking producers
ResiliencePersistence is independent of the realtime channel; per-recipient pushes are settled independently so one failure never blocks the rest
ConfidentialityThe live channel is authenticated and end-to-end encrypted after an encrypted key-exchange handshake
Stable subscription contractA fixed per-recipient channel and a fixed notification topic, so clients subscribe against a stable contract
Cross-instance deliveryA cross-instance fan-out delivers to a recipient wherever their connection is established
i18nThe notification record stores one rendered plain-text + rich-text string; the content seam is event-type-driven so a renderer can localize later, but the one live type currently emits a fixed Vietnamese string rather than per-recipient-language content

8. UX & Flows

The client holds an authenticated, encrypted connection and subscribes to its own private channel; on the notification topic, the activity bell is designed to surface each new notification. An offline recipient finds it waiting via the read API on next load, and marking one - or all - read clears the unread count consistently across sessions.

Client surface status: the backend pipeline, read API, and realtime delivery contract are live, but the in-app notification bell in the client footer is currently disabled - it is not yet surfaced to merchant users. The control exists (a left-footer bell that opens a notification sheet) but ships disabled, so end users do not see in-app notifications today; the merchant-facing surface is a follow-up that consumes this already-shipped backbone.

9. Data & Domain

EntityRole
Notification recordThe persisted, per-recipient record - recipient, type, organizer, content (plain + rich text), action link, structured data, read flag + timestamp
Activity eventThe inbound message on the activity stream - event type, recipient scope, actor, organizer / merchant, optional explicit recipients, payload
Recipient resolutionLooks up the users within the activity's organizer or merchant scope; explicit lists and actor-fallback are honored
Per-recipient channel + topicThe recipient's private real-time channel and the notification-created topic - the live delivery contract

Conceptual only - full schema and invariants live in the developer domain model.

10. Dependencies & Assumptions

Depends on

  • Platform core - owns the notification record definition, the recognized event-type registry, the activity-stream topic and message contract, and recipient resolution from membership data.
  • Activity event stream - the ingest seam between activity producers and the notification worker.
  • Realtime transport + cross-instance fan-out - the authenticated, encrypted, cross-instance live delivery channel.
  • Producing services - publish activity events (e.g. the payment path emits a successful-payment activity).

Assumptions

  • Producers publish well-formed activity events onto the agreed stream topic.
  • Organizer / merchant membership data is available for recipient resolution.
  • Clients maintain a live connection and subscribe to their own per-recipient channel.

11. Risks & Open Questions

Risk / questionMitigation / status
A recipient is offline when the activity firesThe record is persisted regardless; the client re-reads it via the self-scoped API on next load
Realtime channel unavailable or a single push failsPipeline degrades to persistence-only; per-recipient pushes are settled independently so one failure never blocks others
Cross-user leakage in the read APIEvery read / write is confined to the authenticated user - a user cannot reach another's notifications
Cross-tenant channel subscription on the transportKnown hardening follow-up - channel authorization is permissive today; admin/global channels are not emitted to
Channel / topic naming drift between producers and clientsFixed to a per-recipient channel and a fixed notification topic as a stable contract
Only one event type live today (a successful payment)The pipeline is type-driven and reusable; new event types add a renderer without reworking the backbone
In-app client surface not yet liveThe footer notification bell ships disabled - the backbone (delivery, persistence, read API) is shipped, but merchant users do not see in-app notifications until the client surface is enabled as a follow-up

12. Release Plan & Launch Criteria

AspectPlan
PhaseP2 - ACT and WSS in the URD feature catalog
RolloutAll merchants; no feature flag
MigrationNew notification record store; no data migration
Launch criteriaActivity event → recipient resolution → persisted record → live push verified end to end; self-scoped list / count / mark-read / mark-all-read work; a realtime outage degrades to persistence-only; admin connection controls gated by permissions
MonitoringNotification volume, delivery latency, live-connection health, recipient-resolution correctness, push-failure rate

13. FAQ

How is a notification's audience chosen? By the activity's recipient scope - the whole organizer, the whole merchant, or an explicit user list. If none resolves to anyone, the activity's actor receives it.

What if the recipient is offline? The notification is still persisted; the client finds it via the self-scoped read API on next load, and read-state holds across sessions.

Is the live channel secure? Yes - clients authenticate on connect and complete an encrypted key exchange; every payload after the handshake is end-to-end encrypted.

Can a user see someone else's notifications? No - every list, count, and mark-read is confined to the authenticated user.

What if the realtime push fails? Delivery degrades to persistence-only - the record is already written, and one recipient's failure never blocks the others.

Only payments today? The pipeline is event-type-driven. A successful payment is the first live type; new types plug in a content renderer and reuse the whole backbone.

References

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.