Skip to main content

WiseAI Realtor — Notifications System Expected Output Spec

⛔ STATUS: DRAFT — NOT APPROVED. CLAUDE.md Rule #17 (HARD GATE) is NOT satisfied.

Stage-1 agent research, pre-populated from information-architecture.md + design-direction.md + backend-completeness-audit.md (no dedicated mock). Do not build the customer-facing system until the founder approves and confirms the open items in a Stage-2 interview.

Sourced from (read-only, 2026-06-29): backend-completeness-audit.md (P1 "Notifications: an actual system beyond the bell — event taxonomy × channel × per-user prefs + digests; the instant hot-lead push is make-or-break for speed-to-lead"), information-architecture.md, design-direction.md (top-bar notification bell), the approved acceptance/ai-front-desk.md (the existing owner new-lead + emergency SMS/email notification), and acceptance/reviews-engine-mvp.md (the prospect-send gate this spec must NOT cross).


0. Scope — what this spec covers, and what it does NOT

This spec covers the notifications system — how the WiseAI Realtor backend alerts the account's own users (the agent / owner / team) about events that need their attention, across in-app, email, SMS, and push, with per-user preferences and digests. It defines the event taxonomy, the channel matrix, the hot-lead push (speed-to-lead make-or-break), and — critically — the boundary between alerting the account's own staff vs sending to a lead/client/prospect.

Does NOT cover: the content surfaces that events link to (dashboard "Needs a human", inbox, tasks, deals — their own specs); prospect-facing messaging (drips/nurture → realtor-tasks-calendar-mvp.md §4.3; review requests → reviews-engine-mvp.md; inbox replies → realtor-inbox-mvp.md) — those are the founder/owner-pressed, CASL-gated, never-cron sends this spec must never originate.

Build posture: the existing owner notification already fires on lead insert + emergency (SMS + email, ai-front-desk.md §V3/§V4/§L1). This spec generalizes that into a real system (taxonomy × channel × prefs + push + digests). A re_notifications / per-user prefs store is greenfield — not yet in data-model.md (§1, §9).


1. The hard boundary (read this first)

Two different things share the word "notification". Keep them separate.

A. Operational alert (THIS spec)B. Message to a lead/client/prospect (NOT this spec)
RecipientThe account's own user (agent/owner/team)A third-party contact (lead/client/prospect)
Examples"New hot lead", "Aria needs you", "showing request", "missed call", "deal key-date due", "new review"nurture drip, review request, marketing email/SMS, an inbox reply
TriggerEvent-triggered / automatic — this IS the make-or-break hot-lead pushHuman-pressed only — founder/owner presses Send
GateConsent = the staff member's own delivery prefs; it is a transactional alert to the account holder about their own data, not a CEM needing CASL consentCASL/CASL-EBR + never-cron + founder blessing (feedback_never_cron_send_campaigns, feedback_db_writes_ok_sends_need_blessing)
Cron?Yes — a cron/event worker MAY deliver an alert to the account's own user (incl. the hot-lead push)NEVER — no cron may originate a prospect-facing send

⚠️ NEW — flag for founder confirmation (reconciles the batch directive). The batch directive said "ALL outbound (email/SMS/push) is founder-gated and never cron-sent (CASL) — in-app always safe." Read literally that would disable the hot-lead push and the existing ai-front-desk.md owner SMS/email — which are the speed-to-lead engine. The correct boundary (above) is: alerts to the account's own users are event-triggered (incl. SMS/push) and allowed; only messages to a lead/client/prospect are the CASL-gated, never-cron, founder-pressed sends. This spec is written on that boundary; the founder confirms it in Stage-2. If the founder wants even staff SMS/push to be opt-in, that is a per-user preference (§4.3), not a blanket block — the in-app alert always fires; the push for hot leads is the default-on recommendation.


2. The AI-Bridge / honesty anchor

  • An alert tells a human to act; it never acts as the human toward a client. A "new hot lead" push is the bridge prompting the agent to call — it does not auto-message the lead. The only automatic client-facing touches are the consent-gated drip steps (a separate, human-approved system) and the AI's own in-conversation replies (inbox), never a "notification" send.
  • Honest counts. The bell badge + digests reflect real unread/pending events; no fabricated counts.

3. Role-based visibility

BucketRolesNotifications scope
Brokerage managementbrokerage_owner, broker_admin (brokerage scope)Brokerage-wide events (any agent's hot lead, handoff, deal date) + own; configure defaults
Team managementteam_admin (team scope)Team-wide events + own; configure team defaults
Agent / ISAagent, isa (own scope)Events for their assigned leads/deals/conversations + pond items per routing
Support / externaltransaction_coordinator, marketing_assistant, external_partner (bounded)Scoped per policy; external_partner gets in-app only (bounded)

A notification referencing a contact/deal respects the same RBAC as the target surface (no commission/financials leaked in an alert to an agent-scope user; no peer's private lead surfaced).


4. Expected outputs

4.1 — Event taxonomy

Should see — the system fires on these events (each → an alert to the right account user(s), role-scoped):

EventPriorityDefault channels
Hot lead (AI-qualified, high score / high intent)CriticalPush + in-app (+ SMS if enabled) — the make-or-break speed-to-lead alert
New lead (routine capture)NormalIn-app (+ email/digest)
Handoff needed (Aria escalated; "Needs a human")HighPush/in-app — routes to inbox realtor-inbox-mvp.md
Showing request captured (awaiting confirm)HighIn-app/push — routes to Calendar & Tasks (§6: request, not "booked")
Missed call (AI captured a partial / at-capacity)HighIn-app/push
New review / private feedbackNormalIn-app (+ digest) — Reviews reviews-engine-mvp.md
Deal key-date due (financing/inspection/closing)HighIn-app/push (+ email) — Deals realtor-deals-mvp.md
Unactioned-lead SLA breach (> threshold)HighPush/in-app — speed-to-lead enforcement

Should NOT see:

  • An event that auto-sends to the lead/client (alerts go to the agent).
  • An alert that leaks data the recipient's role may not see (§3).

4.2 — The hot-lead push (speed-to-lead make-or-break)

Should see:

  • When a hot lead is captured, the assigned agent receives an instant push (lock-screen, per the mobile mock's "hot-lead lock-screen push") + in-app — fast enough to enable a sub-minute call-back. It deep-links straight to the lead/ conversation with the one-tap actions.
  • The push fires automatically (event-triggered worker) — it is the one automatic channel that must NOT be blocked by a blanket "no auto-send" reading (§1).

Should NOT see:

  • The hot-lead alert delayed into a digest (it is real-time) or suppressed by the prospect-send gate (it is a staff alert, not a prospect send).

Success: a hot lead reaches the right agent's device within seconds, deep-linked to act — the audit's "instant hot-lead push" requirement.


4.3 — Per-user preferences + digests

Should see:

  • A per-user preferences surface: for each event type, the user chooses channels (in-app / email / SMS / push) and timing (instant / digest / off) — with sensible defaults (hot lead = push+in-app on; routine = digest). In-app is always available and is the floor for the most-limited role (external_partner).
  • Digests (e.g. a morning summary, an end-of-day recap) bundling non-urgent events; urgent events (hot lead, handoff, SLA breach) are never demoted into a digest.
  • Quiet hours for non-urgent staff alerts (a user may silence routine pushes overnight); critical alerts may override per the user's choice.

Should NOT see:

  • A preference that silences an in-app critical alert into nothing (in-app remains the floor).
  • A digest that includes a prospect-facing send (digests summarize events, not outbound messages).

Success: each user tunes how they're alerted; urgent events always cut through; quiet hours respected for routine ones.


4.4 — The bell + in-app center

Should see:

  • A top-bar bell with an honest unread badge; an in-app notification center listing events newest-first, each deep-linking to its surface (lead, inbox, task, deal, review), with read/unread + mark-all-read.
  • Counts match reality (the dashboard "Needs a human · 4" and the inbox views stay consistent).

Should NOT see:

  • A bell badge that never clears or shows a fabricated count.

5. Empty / loading / error states

StateExpected output
No notificationsIn-app center shows an honest "You're all caught up" — not a blank panel (anti-pattern #6).
LoadingCenter skeleton rows, not a spinner (anti-pattern #7).
Push not enabledA non-blocking "Enable push for instant hot-lead alerts" prompt; in-app still fires.
Email/SMS delivery failure (to staff)Logged in local_business_message_logs; surfaced as a delivery warning; the in-app alert still fired (the alert is never lost).
Prefs save errorInline retry; prior prefs preserved.

6. Carried-forward constraints (consistent across the batch)

  • Aria does NOT book/schedule — the "showing request" event is a captured request awaiting agent confirmation, never an AI-confirmed booking (relabel; ai-front-desk.md decision 4).
  • SMS: staff SMS alerts are a per-user preference (the account holder's own device); prospect SMS is OFF by default + consent-gated + never cron (§1). Crisis/safety is NEVER gated — a crisis detected in a conversation still surfaces 988 regardless of notification prefs (ai-front-desk.md §V7).
  • No cron may originate a prospect-facing send (feedback_never_cron_send_campaigns); a cron/event worker MAY deliver a staff alert (§1).
  • Role enum (rbac.ts — implemented source of truth): agent | team_admin | transaction_coordinator | broker_admin | brokerage_owner | marketing_assistant | isa | external_partner (scopes own|team|brokerage); alerts respect target-surface RBAC (no commission/peer-lead leakage).
  • No realtor pricing; honest metrics only; language never hardcoded (a multilingual lead's alert can note the language for routing).

7. Accessibility (AODA → WCAG 2.1 AA)

  • Bell + center: keyboard-reachable; the badge has an accessible name ("3 unread"); the center is a navigable list; mark-read is keyboard-operable.
  • Alerts convey priority by text + icon, not color alone; critical alerts use aria-live appropriately (announced, not only visual).
  • Prefs: form controls labelled with state; channel/timing are accessible groups.
  • Push copy is screen-reader-friendly; deep-links land with focus on the relevant action.
  • Contrast ≥4.5:1; gold accent only; reduced-motion honored.

8. Acceptance checklist (QA runs on the deployed URL)

Behavioural verification on wiseaiagency.com (real host) against the demo tenant (…0c01); never "build passes". Sample at ≥2 timepoints.

// The boundary (most important)
test.fixme('an event alert goes to the ACCOUNT user (agent), never auto-sends to the lead/client', () => {});
test.fixme('the hot-lead push fires automatically (event worker) and is NOT blocked by the prospect-send gate', () => {});
test.fixme('NO cron originates a prospect-facing send; the never-cron rule applies only to lead/client messages', () => {});

// Taxonomy + hot-lead push
test.fixme('each taxonomy event fires the right channels to the right (role-scoped) user; deep-links to its surface', () => {});
test.fixme('a hot lead reaches the assigned agent via instant push, deep-linked, fast enough for sub-minute call-back', () => {});
test.fixme('urgent events (hot lead/handoff/SLA breach) are never demoted into a digest', () => {});

// Prefs + digests + RBAC
test.fixme('per-user prefs set channel+timing per event; in-app is the floor; quiet hours silence only routine alerts', () => {});
test.fixme('an alert never leaks commission/peer-lead data the recipient role may not see', () => {});
test.fixme('the bell badge + center counts match reality (consistent with dashboard/inbox); mark-read works', () => {});

// Carried-forward
test.fixme('"showing request" event reads as a captured request, not AI-"booked"; crisis surfaces 988 regardless of prefs', () => {});

// States + a11y
test.fixme('empty/loading/push-off/delivery-failure/prefs-error states render per §5; alert never silently lost', () => {});
test.fixme('bell+center keyboard-navigable; critical alerts announced (aria-live); reduced-motion honored', () => {});

9. Guardrails for agents building against this spec

  • Rule #17 not satisfied — do not build until founder approval.
  • Respect the boundary (§1) — alerts to the account's own users are event-triggered/safe (incl. the hot-lead push); messages to a lead/client/ prospect are founder/owner-pressed, CASL-gated, never-cron. Do not let the notification system become a prospect-send backdoor.
  • Generalize, don't duplicate the existing ai-front-desk.md owner notification — extend it into the taxonomy × channel × prefs system.
  • A re_notifications / per-user prefs store is greenfield — not yet in data-model.md; add it (founder-gated, additive, service-role-grant-only). Migrate-before-use (Rule #18).
  • Aria books nothing; prospect SMS off-by-default + consent-gated; crisis never gated; honest counts; RBAC-respecting alerts; verify on the real host; evidence-or-nothing.
  • If code diverges, update the spec first (founder approval), then the code.

End of spec. STATUS: DRAFT — NOT APPROVED. Open items for Stage-2: (1) confirm the §1 boundary (staff alerts incl. SMS/push are event-triggered; only prospect-facing sends are gated); (2) push infrastructure (PWA web-push vs native); (3) the re_notifications/prefs schema; (4) the hot-lead scoring threshold that triggers the critical push.