0117 — Collections AI pilot: polite delinquency agent for online-portal payers at Camellia
Renumbered from 0112 on 2026-07-28 (collision: this file and
0112-tenant-collections-outreach-lane.mdwere authored in parallel under the same number, neither referencing the other; 0113–0116 are taken — 0117 was the lowest genuinely-free number at renumber time, following the 0113 precedent).Relationship to ADR-0112 (tenant-collections-outreach-lane): the outreach mechanics of this ADR's Phase 1 were built under ADR-0112, which is authoritative wherever the two disagree about the SHIPPED lane (workflow shape, enrollment threshold, gate stack, copy, bake mode). This ADR remains the only record of the broader pilot program: Phase 0 language preference, portal-payer cohort strategy and portal-flag exclusions, promise-to-pay, measurement/kill criteria, counsel sign-off gates, and 10DLC registration. Four of this ADR's Phase 1 claims are not implemented in the shipped lane — each is flagged inline below with a
⚠️ NOT AS SHIPPEDmarker; do not read unmarked Phase 1 mechanics as a description of production either.
AMENDED 2026-08-19 — the INTERNAL demand builder moved to Phase 1. Decision page
collections-three-phases, question d3, answered by Gera: "Pull it into Phase 1. It's the most demoable thing we could build for Hugo and it doesn't touch a resident." This is the single authoritative record of that change; the two places below that describe demand drafting as deferred now point here rather than restating it.What moved. An internal tool that computes the demand's dates, checks it against the statutory element list, and fills a PDF for a human to print, sign and serve by hand. Built at
src/lib/domain/compliance/collections/demand/. The driver is a documented, repeating failure: four Camellia demands bounced in April 2026 over a one-day date error.What did NOT move — none of this is un-gated by the above:
- No demand, and no part of one, is transmitted to a resident. Service is by hand under
C.R.S. § 13-40-108, which does not list electronic delivery, and the module imports nothing that can transmit — pinned bysrc/__tests__/collections-demand-no-send.drift.test.ts.- The counsel gates on the resident-facing collections lane are untouched.
- Guarantor contact, payment-plan self-service and outbound voice stay deferred exactly as written below.
- The module's own counting rules are marked
counselVerified: falseand every packet says so, so nothing here asserts counsel review that has not happened.Still open for a human: whether "counsel review of any future 10-day-demand drafting" was ever meant to reach an internal drafting aid that no resident sees, or only the resident-facing capability. Engineering has not decided that and this amendment does not decide it — it records the scope shipped so the question is answerable against something concrete.
- Status: Proposed
- Date: 2026-07-23
- Deciders: Fede, Sean, Gera
- Owner: Gera
- Origin: Founders call 2026-07-23 (Sean: "we could do, like, a collections AI... that's a feature we can sell"; decision: "for now, turnovers and delinquency... we practice at Camellia"). Market driver: EliseAI and every serious competitor ship a delinquency product and publish delinquency-reduction claims.
Context
Why now
- Product/market: Every credible competitor has this. EliseAI (ResidentAI Delinquency) claims
40–52% delinquency reduction and names Brookfield, Equity Residential, AvalonBay as adopters. Entrata acquired Colleen AI (−40% unpaid rent claim) and made it the spearhead of ELI+. RealPage ships Lumina AI payment reminders. AppFolio itself ships "Delinquency RealmX flows." Sean's framing: a sellable add-on line item ($0.25/unit/mo idea from the 07-23 call). Vendor metrics are self-reported and un-audited — treat as directional, not benchmarks. - Customer: "There's quite a bit of delinquency at Camellia, so it could help there" (Sean, 07-23). JP leadership (Darrin) has asked for it previously.
- Platform readiness: The hard read-side is already built and battle-tested via renewals — ledger-accurate
getDelinquencyProfile()from AppFoliocharge_detail, hourlyTenantBalanceSnapshotsync with aged-AR buckets, per-property threshold config, consent-gated outbound dispatcher (SMS/email/voice), and Temporal-driven autonomous-send precedent.
The Camellia realities we must design around (from 07-23 founders call)
- Money orders. A large share of residents are unbanked and pay by money order; paper takes ~a week from office to bank. Some split rent into two partial payments (e.g., on the 15th). A naive "you're late" ping will fire at people who already paid. Fede: "we might be calling someone, hey, you're late, but they already gave the check."
- Late-is-normal culture. "A lot of people are late. But that's kind of like the expected."
- Partial payments are a normal pattern, not an anomaly.
- Language: a meaningful share of residents are Spanish-preferring (07-23 morning JP call: mass-send about water shutoff went out in English to Spanish-speaking units; team agreed language preference + auto-translation is needed).
- Adoption: any feature requiring the JP team to maintain per-tenant config will rot. Config must be set once by us, with per-unit opt-out at most.
What the platform has today (verified 2026-07-23, repo inspection)
Exists and reusable:
DelinquencyProfile(src/lib/domain/leasing/delinquency/index.ts:30) — ledger-accuratebalanceMonths,pastDueRent,latePayments(12-mo), computed live from AppFoliocharge_detailviacomputeRenewalFeatures; fails closed (null = unknown, never "not delinquent").TenantBalanceSnapshot(src/lib/data/types.ts:1557) withbucketedARaging buckets, hourlylambda/appfolio-sync,refuseZeroRegressionguard (re-cut 2026-08-19 — an ATTESTED $0 now writes, so a paid-off account leaves the list; what is refused is an amount the feed never stated, plus every zero in a tick whose whole feed collapsed. The prior blanket refusal was a one-way ratchet that froze $43,683.70 of already-paid debt across 35 live accounts); canonical readgetCurrentBalance()(src/lib/domain/pms/balance-read.ts:34).- Threshold-config pattern:
RenewalPolicyThresholds(maxBalanceMonths2.0,maxLatePayments2,lateGraceDays10) and per-propertydelinquencyThresholdMonths(types.ts:1031). - Tenant-facing
lease_and_billing.get_tenant_balancetool with server-resolved identity boundary (never LLM-supplied), dual-channel text+voice. - Outbound rails all wired through one dispatcher (
agents/clara/lib/messaging/dispatch-envelope.ts): Twilio SMS (TCPA consent viagetSmsConsentByPhone), Microsoft Graph email, ElevenLabs voice (session adapter + separate call-initiation path). - Charge-write precedent to AppFolio via browser agent (move-out charges, ADR-0044).
Missing (net-new):
- AppFolio
delinquencyreport read —AppfolioDelinquencyRow(integrations/appfolio/types.ts:351) is fully typed (PaymentPlan,InCollections,CollectionsAgency,NSF,LateCount,LastPayment,DelinquencyNotes) but nothing calls the endpoint. (✅ Partially wired 2026-07-29:AppfolioPMSClient.listCollectionsAccountStatusesreads the report for the In-Collections / Certified-Funds-Only exclusion flags — see the exclusions note under Phase 1. The rest of the report —PaymentPlan, aging buckets,NSF— remains unread.) - Payment-history read (
get_payment_historyexplicitly descoped inhandle-get-tenant-balance.ts:37-41;lastPaymentAmount/Datehardcoded null). - Any proactive collections outreach flow (today's only delinquency "action" is a passive FLAG on the renewals board).
- Payment-plan / promise-to-pay / NSF domain model.
- Pay-now link path.
daysDelinquentis naive (assumes due-on-1st, ignores aging buckets).- Per-tenant ledger/AR UI.
- Tenant language preference + outbound translation layer.
AppFolio mechanics that constrain the design (KB-verified)
- The Data API is read-only. Every mutation (post/waive fees, credits, payment plans, notices, status flags, Place Into Collections) is UI-only → browser automation or human. Reads we need:
delinquency,aged_receivables_detail(wired),charge_detail(wired),payment_plans,receivables_activity,tenant_debt_collections_status. - Unapplied prepayments still read as unpaid — "already paid" suppression must check applied payments, not money-on-account.
- Evicting / In Collections / Certified Funds Only flags disable online portal payments — never send a "pay in the portal" link to a flagged tenant.
- Payment plan in AppFolio = edit charge → split into dated child charges; tracked via
PaymentPlancolumn andpayment_plansreport. - Late-fee engine: property-level policy, tenant overrides, base fee posts once per charge, auto-posting available; unpaid-charge reminder emails exist but don't log to the tenant activity trail (AppFolio's own answer is RealmX flows — both our integration surface and our competitor).
- Hidden flag suppresses tenant ops — check visibility before acting.
- Collections handoff (Hunter Warfield) requires past occupancy, >$100, <2yr, DOB/SSN — post-resident territory, out of scope for this pilot.
Regulatory posture (research 2026-07-23; counsel sign-off required before go-live; not legal advice)
- FDCPA / Colorado CFDCPA / CFPB Reg F: do NOT bind a first-party landlord/PM collecting its own current-resident rent — with two trap doors: (a) never present under a name implying a third-party collector; (b) debt acquired already-in-default makes us a "debt collector." Counsel must confirm the JP-Co/owner-entity structure keeps us first-party. We self-impose the Reg F cadence caps anyway (≤7 contact attempts per 7 days, cooldown after a live payment conversation).
- Colorado late-fee law (C.R.S. §38-12-105) binds hard: fee = greater of $50 or 5%; ≥7-day grace; must be in the lease; one fee per late payment; no fee-on-fee/interest; payments apply to rent first ("anti-rent-skimming"); written notice of fee within 180 days; no eviction for unpaid late fees; $50/violation penalty + CCPA deceptive-practice exposure.
- Eviction: nonpayment requires a written 10-day pay-or-quit demand (C.R.S. §13-40-104(1)(d)); tenant cure within 10 days kills that notice. Clara must never imply eviction is imminent before a valid demand exists.
- TCPA/FCC binds fully: consent (number provided in connection with the lease suffices for transactional debt contact; email consent ≠ SMS consent); quiet hours 8am–9pm resident-local; opt-out by any reasonable means honored ≤10 business days, cross-channel; AI voice = "artificial voice" (highest bar — prior express consent; treat voice as last channel); 10DLC registration of collections as its own transactional campaign; keep consent/opt-out records ≥4 years.
- Colorado AI Act: SB24-205 repealed; replacement SB26-189 disclosure duties effective Jan 1, 2027 — no statutory AI-disclosure mandate during the pilot, but we disclose anyway (UDAP-deception hedge; EliseAI's human-name-no-disclosure approach was publicly called "unfair and deceptive" by NHLP's litigation director — we take the opposite position as a trust feature).
- Fair housing: identical cadence/tone/escalation for all similarly-situated delinquent residents; audit outcome distributions; benefit-cycle payment timing (e.g., SSDI mid-month) must not be penalized harder; language access — English-only collections at a heavily-Spanish property is a national-origin disparate-impact risk and a bad experience. Hence Phase 0.
- Payment plans: partial-payment acceptance can waive eviction rights on a notice → counsel-approved reservation-of-rights template; plans only from a pre-approved parameter menu; payments book to rent first; written confirmation of every plan.
Market mechanics worth copying / avoiding (research 2026-07-23)
- Copy: operator-configurable cadence (not fixed ladders); tone that adapts to payment history (first-time late ≠ chronic); balance questions answered inside any existing thread; guarantor/co-signer awareness; PM alert thresholds with drafted-not-sent legal notices; 24/7 responsiveness.
- Industry's weakest documented dimension = payment-plan guardrails (nobody publishes approval authority, dollar/term limits, or promise-to-pay kept/broken tracking). Explicit PM-configurable guardrails are a differentiation opportunity.
- Avoid: undisclosed AI with a human name (EliseAI's exposed flank); fixed aggressive ladders; touching post-resident FDCPA recovery (Pay Ready's territory, different regime).
- Flexible-rent rails (Flex, Best Egg) are a different category — prevention via rent-splitting, resident-paid fees. Not this ADR; note
TenantPmsSnapshot.occupancy.flexibleRentEnabledalready exists if we ever surface them.
Decision
We build a collections agent as a Clara capability ("rent reminders", never "collections" in any tenant- or PM-facing copy), piloted at Camellia on a deliberately narrow population, with tenant language preference shipped first. Gera owns the build end-to-end.
Phase 0 — Tenant language preference (prerequisite, ships first)
Per-tenant languagePreference ('en' | 'es', extensible) on the tenant record:
- Sourced from: application data where present, conversation-inferred (Clara detects the tenant writes/speaks Spanish), and PM-settable.
- All outbound tenant messaging (not just collections) checks preference and translates before send; mass-send UI shows the translated per-language version for PM review before sending (Gera's 07-23 morning-call point: raw MT can be bad — PM can tweak).
- Fair-housing rationale: LEP residents must not land on a de-facto harsher track because the agent couldn't understand them.
Phase 1 — Pilot population and trigger
Population: Camellia residents who pay via the AppFolio online portal only. Payment state is real-time; no money-order ambiguity. (~40% of Camellia per Fede's 07-23 estimate — verify against
receivables_activitybefore launch.) Determination from payment-history tender types, refreshed monthly.Exclusions (hard): money-order/check payers; Evicting / In Collections / Certified Funds Only flags (portal is disabled for them anyway); Hidden tenants; anyone with an applied-payment-verified zero balance; active disputes; per-unit PM opt-out list (mass-send-style unit picker); anyone who has opted out of the channel.
✅ IMPLEMENTED 2026-07-29 — In Collections + Certified Funds Only (the two account-flag exclusions; ADR-0112's 2026-07-29 addendum is the authoritative record). Read LIVE from the AppFolio
delinquencyreport via the PMS adapter (collectionsStatuscapability) — never the manual-syncTenantPmsSnapshot, which has no automatic writer — and enforced at BOTH enrollment (counted walker skips) and fire time (cadence stop, re-read per touch). "Evicting" was already covered by the shipped lane'sexitingstop (lease.evictionPending). Still NOT implemented: the portal-payer cohort restriction, money-order/check-payer exclusion, Hidden tenants, active disputes, and the per-unit PM opt-out list.Trigger: rent charge unpaid (applied payments only) past
reminderGraceDays(default: after Colorado's 7-day statutory grace, i.e., day 8+), balance ≥minReminderBalance(default $50, matching the platform delinquency predicate).All thresholds live in per-property config following the
RenewalPolicyThresholdspattern; we set Camellia's values — zero JP configuration burden.
Phase 1 — Cadence and content (the "not annoying" contract)
Max 3 touches per month per resident, self-imposed ceiling well under Reg F's 7-in-7:
⚠️ NOT AS SHIPPED (verified 2026-07-28). The built lane (ADR-0112) has no frequency cap: the daily walker re-enrolls a still-delinquent tenant after each cadence exhausts (
USE_EXISTINGdedupes only a running cadence; the lane explicitly setsworkflowIdReusePolicy: 'ALLOW_DUPLICATE'incollections-chase-client.ts, permitting a fresh run once one completes), so a tenant who stays past due can receive on the order of ~12 touches/month under the default+1h/+3d/+7dcadence. A re-enrollment frequency cap is being implemented in a separate change — this note records the gap; it does not resolve it.⚠️ The day-8/15/22 ladder below is also NOT AS SHIPPED. The shipped cadence is
DEFAULT_COLLECTIONS_TOUCHES(+1hafter enrollment,+3d,+7d— enrollment itself atbalanceMonths >= 1, well past day 22), the copy is one gentle template for every touch (ADR-0112 §3.6 Option A, not a firming three-step), and no day-22 PM alert exists — the only PM escalation in the lane fires on a tenant reply (ADR-0112 §3.5), not on cadence exhaustion.- Day 8 (post-grace): friendly SMS (or email if no SMS consent), tenant's preferred language: balance + portal pay link + "reply if something's wrong." Never the word "late fee" unless one has actually posted and is lease-valid and cap-compliant.
- Day 15: second reminder, slightly firmer, still service-toned; offers help/payment-conversation explicitly.
- Day 22: final reminder; notes the team may follow up personally; PM alerted in-app + digest.
Stop conditions (any): applied payment ≥ balance; promise-to-pay recorded (then one polite check-in only if the promise date passes unpaid); opt-out; dispute raised (→ human immediately); PM pause.
Tone: Sean's 07-23 spec verbatim as the north star: "hey, it's Clara, we noticed we haven't received your payment yet, here's a link, let us know if there's any issues." Adaptive: first-time-late gets the softest copy; chronic-late gets neutral-firm — never harassment-adjacent. Tone bank goes through promptfoo evals before launch.
AI disclosure: every first-contact thread identifies Clara as Camellia's automated assistant with an easy human handoff. Differentiator, not fine print.
⚠️ NOT AS SHIPPED — open product/legal decision (owner: Gera). The shipped dun (ADR-0112,
collections-chase-copy.ts) says only "this is {property}"; no shipped tenant SMS lane proactively discloses AI, and PropFlow's house policy is reactive honesty (clara-personality.ts— answer honestly if asked, never volunteer). This ADR proposed the opposite (proactive disclosure as a trust feature). Both are defensible; nobody has decided which governs collections. Relevant deadline: Colorado SB26-189 makes some AI disclosure mandatory 2027-01-01, so the question has a clock but not an answer. This note frames the divergence — it does not decide it.Quiet hours: 8am–9pm resident-local, resolved by area code. (Enforced at the approval/send rung —
decideCollectionsDunPremise— not in the dispatcher;dispatchapplies consent, suppression and sender policy only and never looks at the clock.)✅ AS SHIPPED since 2026-08-19 — see ADR-0129.
decideCollectionsDunPremiseresolves the RESIDENT's own IANA zone from their phone number and refuses the send outright when the area code does not establish one. There is no fallback zone on that path.This note previously read ⚠️ NOT AS SHIPPED, and its reconciliation was wrong in a way worth keeping on the record: it said the built hold was property-local but that "for resident tenants, the resident lives at the property, so property-local ≡ recipient-local in practice." The equivalence assumed a resident is at the building right now, which the statute does not — and it rested on a field with no writer in application code. (
Property.timezoneis populated on all 17 prod rows today, checked 2026-08-19, so the Central fallback was latent rather than firing; the punchier "it defaulted to Chicago for everyone" is false and should not be repeated.)⚠️ Still property-local, deliberately out of ADR-0129's scope: renewals, prospect outreach, maintenance comms, handyman paging, and the composition-pacing hold in
collections-chase-workflow.ts(which gates when a row reaches the review QUEUE, not when a resident is texted).Channels: SMS + email only in the pilot. No outbound voice (FCC artificial-voice regime; revisit post-pilot with explicit sign-off).
Clara answers balance questions in any existing thread via the existing
get_tenant_balancetool (upgraded: reallastPayment*from payment history, bucket-awaredaysDelinquent).
Phase 1 — Human-in-the-loop boundaries
The agent may autonomously: send reminders per cadence, answer balance/lease questions, record promises-to-pay, surface the portal link. The agent may never: mention or threaten eviction/legal action/credit reporting; state a late fee that isn't posted+lease-valid+cap-compliant; offer or accept payment-plan terms (pilot: plans are drafted as suggestions to the PM from a pre-approved parameter menu, human sends/enters them in AppFolio); waive anything; contact guarantors (post-pilot); touch the 10-day demand (post-pilot candidate: draft the demand for human review/service — never send). ⚠️ PARTLY SUPERSEDED 2026-08-19 — the INTERNAL demand builder shipped; see the AMENDED banner at the top of this ADR for exactly what moved and what did not. The "never send" half is unchanged and is now enforced structurally. Escalation to PM: dispute, hardship mention, opt-out, promise broken twice, day-22 reached (⚠️ not as shipped — there is no day-22 alert, per the cadence note above; the only shipped PM escalation fires on a tenant reply, ADR-0112 §3.5), any eviction/legal question, LEP conversation the agent can't hold.
Phase 1 — Data/infra build
- Wire the AppFolio
delinquencyreport + payment history (receivables_activity/charge-receipt join) through the PMS adapter (getPMSClientregistry — no direct imports). New adapter methods on the domain interface so Yardi/RealPage can implement later. CollectionsCaseper tenant-month: state machineELIGIBLE → REMINDED_1..3 → PAID | PROMISE | ESCALATED | SUPPRESSED, full audit trail (message, timestamp, channel, ledger snapshot backing every stated balance). Records retained ≥4 years (TCPA SOL).- Promise-to-pay record: amount, date, kept/broken, linked to the case.
- Cadence runs on Temporal (same doctrine as renewals — ADR-0108 spine; the 2026-05-26 SQS-bypass incident is the cautionary tale).
- Suppression check at send time, not enqueue time (balance re-read with applied-payment verification immediately before dispatch).
Phase 1 — Measurement (proving "we can reduce delinquency")
Baseline locked before launch from ledger history (charge_detail, 12 months back):
- Primary: % of pilot-population rent collected by day 15 / day 30 vs. trailing-6-month baseline for the same tenants (within-subject), and vs. the money-order (non-pilot) population as a concurrent non-equivalent control. Camellia's portal cohort is small (~40% of ~112 units ≈ 40-45 tenants) — report absolute numbers with the caveat, not just percentages; statistical significance is unlikely in month 1, direction + case-level narrative is the pilot bar.
- Secondary: days-sales-outstanding proxy (avg days from due date to applied payment); # of promises made/kept; % of cases resolved without PM touch; escalation rate.
- Experience guardrail metrics (kill criteria): opt-out rate >5% of contacted residents, any validated wrong-balance message, >2 complaints to the JP office, or any fair-housing-relevant tone/outcome disparity in the monthly audit → pause and review.
- Per pm-domain-knowledge benchmarks: track bad-debt-%-of-GPR and resident delinquency headcount as separate metrics against the property's own trailing baseline (never national fixed thresholds); ≥4-6 weeks of history before trusting a trend.
Entity classification (per ADR-0027)
| Entity | Class | Naming | Spine trace (canonical) OR derived-from / rebuilt-by / drift-tolerance (derived) |
|---|---|---|---|
CollectionsCase |
canonical | bare name | Spine trace: personId + propertyId; lifecycle events appended by the Temporal workflow |
PromiseToPay |
canonical | bare name | Spine trace: via collectionsCaseId → personId |
TenantLanguagePreference (field, not entity — lives on tenant record) |
canonical | field languagePreference |
Spine trace: on the tenant record directly |
PaymentHistorySnapshot |
derived | *Snapshot suffix |
from: AppFolio receivables_activity/charge_detail rows · Rebuilt by: lambda/appfolio-sync · Drift tolerance: ≤1h (same as balance snapshots) |
Consequences
- Commits us to the first-party posture: Clara is always "Clara at Camellia Apartments," never a collector persona; no post-resident recovery in this product line without a separate ADR (different legal regime).
- Commits us to the disclosure-forward stance as brand: we say it's AI; competitors mostly don't.
- Phase 0 language preference benefits every existing flow (renewals, maintenance, mass-send) — it is not collections-scoped, and translation review UX must land in the mass-send composer.
- New standing operational duties: monthly fairness audit of cadence/tone/outcomes; consent/opt-out record retention; counsel review of tone bank + plan template + any future 10-day-demand drafting before each capability un-gates.
- 10DLC: collections messaging registered as its own transactional campaign before first send. Note the T-Mobile SIP-603 block on the Camellia TFN affects voice only, but SMS deliverability must be smoke-tested on all major carriers before launch.
- Pricing/packaging (Sean): sellable add-on; pilot proves the metric story first. No customer-facing pricing commitment in this ADR.
- Eviction-adjacent automation (10-day-demand drafting, guarantor contact, payment-plan self-service, outbound voice) is explicitly deferred — each un-gates only with counsel sign-off + Fede/Sean approval. ⚠️ PARTLY SUPERSEDED 2026-08-19 for the drafting item only: the INTERNAL demand builder moved to Phase 1 by owner decision. Guarantor contact, payment-plan self-service and outbound voice are unchanged. See the AMENDED banner at the top of this ADR.
- Eval discipline: tone bank + suppression logic get promptfoo cases (
evals/datasets/); every classification bug found becomes an eval case; live tests only against Test Property (ID 45) or log-mode per the no-real-comms rule until launch day.
Alternatives considered
- Whole-building blast with per-unit opt-out (Sean's first framing, 07-23). Rejected for the pilot: money-order timing makes false "you're late" pings likely, and false dunning is the single fastest way to burn resident trust and create UDAP exposure. Revisit once money-order logging latency is instrumented.
- Wait for Yale (build for a "normal" 95%-online property). Rejected: Camellia is live today, has real delinquency, and the harder constraint set produces a more general design; Sean's counterpoint ("built for the next people") is captured by keeping all thresholds per-property config.
- Fixed escalation ladder (3/6/15/30-style) as marketed by overlay vendors. Rejected: fights Colorado's 7-day grace and Camellia's norms; config-driven cadence matches what EliseAI/Colleen actually ship.
- Autonomous payment plans in the pilot. Rejected: waiver-of-rights risk on partial payments, AppFolio plan-entry is browser-automation-only, and the industry's own opacity here signals it's the hard part. Plans are human-approved suggestions in the pilot.
- Outbound voice from day one ("call them in Spanish"). Rejected for pilot: FCC artificial-voice consent regime + T-Mobile TFN block; SMS/email prove the concept at lower risk.
- Buy/embed a flexible-rent rail (Flex/Best Egg) instead of building. Different problem (prevention vs. recovery), resident-paid fees, no agent conversation. Complementary, not a substitute; not this ADR.
- AppFolio RealmX delinquency flows. Solves outreach inside AppFolio but is PMS-locked, un-differentiating, and cedes the resident conversation (and the audit trail) to the PMS — strategically the opposite of our position.