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) — perdata-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; there_contacts.*_consent_statusrollup is a cache recomputed from it (never written directly).local_business_voice_lines— ADDsms_enabled boolean,provider text DEFAULT 'telnyx'.- Each SMS also writes a thin
re_interactions(channel='sms')row so the unified timeline stays complete.
4. Consent model (the load-bearing piece — CASL/PIPEDA)
- Every outbound SMS is consent-gated. Before send, resolve the contact's SMS
consent from the
re_consent_eventsledger (via the rollup). Allowed bases:express, orimplied_inquiry(the lead texted/inquired first — a live inbound conversation window),implied_existing_relationship,implied_transaction. No valid basis → send is blocked andconsent_checkedrecords the failed gate; never send anyway. - STOP / STOP-family keywords on inbound → write a
consent_action='withdrawn're_consent_eventsrow (channelsms), 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)
- Inbound: Telnyx delivers an inbound SMS to a signed webhook
(
/api/realtor/sms/inbound, signature-verified) → resolve or create there_contactsrow by number → storere_sms_messages(direction='inbound', status='received')+re_interactions. If STOP/HELP → handle per §4 and stop. - 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.
- Outbound send: run the consent gate (§4); on pass, send via the Telnyx
Messaging API from the tenant's
phone_line_idDID; writere_sms_messages(direction='outbound', status='queued', is_automated, consent_checked=true). - Status callbacks: Telnyx delivery webhook updates the row
(
sent|delivered|failed) +provider_message_id,segments. - 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
- Inbound threads: an inbound text to a tenant DID creates/links a
re_contactsrow and storesre_sms_messages(inbound)+ are_interactions(sms)row; it appears in the Inbox SMS channel. - Outbound is consent-gated: an outbound send to a contact with no valid SMS
consent basis is blocked (not sent;
consent_checkedaudits the block). A contact who texted in first (implied_inquiry) can be replied to. - STOP fails closed: texting STOP writes a
withdrawnre_consent_eventsrow, sends exactly one confirmation, and every subsequent outbound to that number is suppressed. - HELP returns the business-identity auto-reply (no marketing).
- Delivery status: an outbound row transitions
queued → sent → delivered(orfailed) from Telnyx callbacks, withprovider_message_idset. - Telnyx, not Twilio: sends use the Telnyx Messaging API on the tenant's existing DID; no Twilio dependency is introduced.
- No cron sends: there is no scheduled/bulk send path; Aria only texts within an active inbound window or on explicit agent action.
- account_id + UPSERT: authed client calls carry
?account_id=; line-config writes UPSERT. - 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_interactionsrows 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
withdrawnre_consent_eventsrow 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.