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 approvedacceptance/ai-front-desk.md(the existing owner new-lead + emergency SMS/email notification), andacceptance/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) | |
|---|---|---|
| Recipient | The 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 |
| Trigger | Event-triggered / automatic — this IS the make-or-break hot-lead push | Human-pressed only — founder/owner presses Send |
| Gate | Consent = 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 consent | CASL/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.mdowner 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
| Bucket | Roles | Notifications scope |
|---|---|---|
| Brokerage management | brokerage_owner, broker_admin (brokerage scope) | Brokerage-wide events (any agent's hot lead, handoff, deal date) + own; configure defaults |
| Team management | team_admin (team scope) | Team-wide events + own; configure team defaults |
| Agent / ISA | agent, isa (own scope) | Events for their assigned leads/deals/conversations + pond items per routing |
| Support / external | transaction_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):
| Event | Priority | Default channels |
|---|---|---|
| Hot lead (AI-qualified, high score / high intent) | Critical | Push + in-app (+ SMS if enabled) — the make-or-break speed-to-lead alert |
| New lead (routine capture) | Normal | In-app (+ email/digest) |
| Handoff needed (Aria escalated; "Needs a human") | High | Push/in-app — routes to inbox realtor-inbox-mvp.md |
| Showing request captured (awaiting confirm) | High | In-app/push — routes to Calendar & Tasks (§6: request, not "booked") |
| Missed call (AI captured a partial / at-capacity) | High | In-app/push |
| New review / private feedback | Normal | In-app (+ digest) — Reviews reviews-engine-mvp.md |
| Deal key-date due (financing/inspection/closing) | High | In-app/push (+ email) — Deals realtor-deals-mvp.md |
| Unactioned-lead SLA breach (> threshold) | High | Push/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
| State | Expected output |
|---|---|
| No notifications | In-app center shows an honest "You're all caught up" — not a blank panel (anti-pattern #6). |
| Loading | Center skeleton rows, not a spinner (anti-pattern #7). |
| Push not enabled | A 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 error | Inline 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.mddecision 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-liveappropriately (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.mdowner notification — extend it into the taxonomy × channel × prefs system. - A
re_notifications/ per-user prefs store is greenfield — not yet indata-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.