Skip to main content

Acceptance Spec — Realtor Programmatic SMS / Text-Back (MVP)

  • Status: Proposed (MVP) — 2026-07-03
  • Product: WiseAI Realtor System → AI front office (Aria), SMS/text-ISA lane
  • Owner surface: /realtor/app/inbox (SMS channel) + /realtor/app/settings/agents (SMS working mode) + Aria's SMS tool
  • Priority: NOW #2 (highest-ROI front-office behaviour; reuses owned Telnyx infra — AUDIT_realtor_integrations_strategy_2026-07-03.md §2 Comms, §5)
  • Related: ai-front-desk, realtor-calendar-booking-mvp, data-model.md §1.3/§1.9

1. Why (problem)

Text is the channel realtors actually convert on, and "missed-call text-back" + speed-to-lead is table stakes. Today the platform has no programmatic outbound SMS for realtor leads — the calendar/CRM rail only emits device-native sms: links that open the agent's own phone. re_sms_messages (threaded, consent-gated) is design-only in data-model.md §1.9. This MVP builds a real two-way SMS lane on Telnyx (already in our voice stack — do NOT add Twilio): Aria texts a new lead back within seconds, qualifies, and books — as AI, handing off to a human.

2. Scope

In: inbound SMS webhook (Telnyx) threaded into re_sms_messages + re_interactions(channel='sms'); outbound send (agent-typed in co-pilot; Aria auto-reply in autopilot) with a CASL consent gate on every outbound; STOP / HELP keyword handling → re_consent_events; delivery-status callbacks; per-line sms_enabled + provider='telnyx'. Bound to the existing per-tenant local_business_voice_lines DIDs.

Out (explicit non-goals): bulk/marketing SMS blasts and any cron-triggered send (founder-press-only gate never lifts — CLAUDE.md); WhatsApp (LATER — reuses this consent plumbing); Twilio (Telnyx only); automated 10DLC/A2P campaign registration (an ops task, not this build); MMS campaigns (single MMS inbound handling only).

3. Data shape (already specified in data-model.md — build it)

  • re_sms_messages (NEW) — per data-model.md §1.9: direction, from_number /to_number (E.164), body, status (queued|sent|delivered|failed|received), provider/provider_message_id, is_automated, consent_checked (outbound gate audit), agent_id (NULL = AI ISA), phone_line_id, contact_id.
  • re_consent_events (per §1.3) — append-only CASL/PIPEDA ledger; the re_contacts.*_consent_status rollup is a cache recomputed from it (never written directly).
  • local_business_voice_lines — ADD sms_enabled boolean, provider text DEFAULT 'telnyx'.
  • Each SMS also writes a thin re_interactions(channel='sms') row so the unified timeline stays complete.
  • Every outbound SMS is consent-gated. Before send, resolve the contact's SMS consent from the re_consent_events ledger (via the rollup). Allowed bases: express, or implied_inquiry (the lead texted/inquired first — a live inbound conversation window), implied_existing_relationship, implied_transaction. No valid basis → send is blocked and consent_checked records the failed gate; never send anyway.
  • STOP / STOP-family keywords on inbound → write a consent_action='withdrawn' re_consent_events row (channel sms), send the one mandatory confirmation, and suppress all further outbound to that number. Fails closed.
  • HELP → auto-reply with the business identity + how to reach a human (no marketing content).
  • Aria autopilot texts only within an active inbound conversation window (implied consent from the lead's own inbound). Any marketing-scope or cold first-touch text is out of scope (founder-gate).
  • Consent wording shown/most-recent basis is captured in re_consent_events.evidence.

5. Message flow (states)

  1. Inbound: Telnyx delivers an inbound SMS to a signed webhook (/api/realtor/sms/inbound, signature-verified) → resolve or create the re_contacts row by number → store re_sms_messages(direction='inbound', status='received') + re_interactions. If STOP/HELP → handle per §4 and stop.
  2. Aria responds: co-pilot → Aria drafts a reply the agent approves in the Inbox; autopilot → Aria replies on her own within the window, identifying as AI.
  3. Outbound send: run the consent gate (§4); on pass, send via the Telnyx Messaging API from the tenant's phone_line_id DID; write re_sms_messages(direction='outbound', status='queued', is_automated, consent_checked=true).
  4. Status callbacks: Telnyx delivery webhook updates the row (sent|delivered|failed) + provider_message_id, segments.
  5. Escalation: licensed-agent questions (price/legal/condition) → Aria escalates to the human (mirrors the chat/voice escalation), it lands in Needs a human.

6. AI-Bridge guardrail

Aria texts as AI, always identifies as an AI assistant, never gives legal/mortgage/tax advice or quotes a value over SMS, and hands off tricky questions to the licensed human (same guardrail set enforced on chat/voice). Texts are a bridge to a person, not a substitute. The universal crisis/safety response applies to SMS too.

7. Honest status hub

The Integrations hub "SMS" capability flips to enabled for a tenant only when a DID has sms_enabled=true AND a live Telnyx send/inbound round-trip has succeeded for that line — not on config alone.

8. Acceptance criteria

  1. Inbound threads: an inbound text to a tenant DID creates/links a re_contacts row and stores re_sms_messages(inbound) + a re_interactions(sms) row; it appears in the Inbox SMS channel.
  2. Outbound is consent-gated: an outbound send to a contact with no valid SMS consent basis is blocked (not sent; consent_checked audits the block). A contact who texted in first (implied_inquiry) can be replied to.
  3. STOP fails closed: texting STOP writes a withdrawn re_consent_events row, sends exactly one confirmation, and every subsequent outbound to that number is suppressed.
  4. HELP returns the business-identity auto-reply (no marketing).
  5. Delivery status: an outbound row transitions queued → sent → delivered (or failed) from Telnyx callbacks, with provider_message_id set.
  6. Telnyx, not Twilio: sends use the Telnyx Messaging API on the tenant's existing DID; no Twilio dependency is introduced.
  7. No cron sends: there is no scheduled/bulk send path; Aria only texts within an active inbound window or on explicit agent action.
  8. account_id + UPSERT: authed client calls carry ?account_id=; line-config writes UPSERT.
  9. Bridge: Aria's texts identify as AI and escalate licensed-agent questions to a human.

9. Verification (deployed URL)

  • Using a Telnyx test/dev number against the TeamMoelker showcase account on the real host: send an inbound text → confirm the re_sms_messages + re_interactions rows and the Inbox SMS thread; reply via Aria → confirm outbound row + delivery-status transition. Sample at ≥2 timepoints (queued → delivered).
  • STOP test: text STOP → assert a withdrawn re_consent_events row and that a follow-up outbound is blocked (query the DB, don't trust the UI alone).
  • Negative: attempt outbound to a no-consent contact → assert it is blocked and audited, not sent.