Nine places decide whether it is too late or too early to message someone, and they disagree. This page plans one rule in one place. Nothing is being built yet.
Proposed — pending review · Not scheduled — planned only · 2026-10-06
"I don't want different logic spread out… if it's not centralized, we should do it first. So it'll be a refactor. And then we should pass in overrides or exceptions… another example could be an emergency."
Exceptions come after the refactor. Not approved yet ("don't add that yet").
"the quiet hour should conform to that TCPA regulation."
One window: 8 AM to 9 PM, on the recipient's clock.
"I don't think we should build it right now."
This page is the plan only.
| # | Where | Hours and whose clock | At night |
|---|---|---|---|
| 1 | Single sends to prospects and residents | 8 AM–9 PM, building clock, guesses Chicago if no zone | Caller decides |
| 2 | Renewal messages to residents | 8 AM–9 PM, building clock, refuses if no zone | Skips; the workflow waits |
| 3 | Waiting-message holds for prospects (follow-ups, tour nudges, collections and vendor chasers) | 8 AM–9 PM, building clock, guesses Chicago | Holds until 8 AM |
| 4 | Held and approved texts (prospects, residents, vendors) | 8 AM–9 PM, building clock, refuses if no zone | Holds until 8 AM |
| 5 | Handyman and vendor dispatch | Per handyman, default quiet 9 PM–7 AM, building clock | Holds. Emergencies always go through |
| 6 | Collections legal check | 8 AM–9 PM, building clock (area code only as a cross-check), no guess | Refuses |
| 7 | Collections rule check | Hour table, hour handed in | Adds a note only |
| 8 | Self-guided staff notes | Sends at any hour (temporary, Fede 10/06). Was quiet 9 PM–7 AM, building clock | Sends |
| 9 | Mass texts to residents | Flagged late 9 PM–7 AM, building clock, guesses Chicago | Warns the manager, sends anyway if they say now |
Not scheduled — planned only Each PR is one concern, about 300 lines or less, and merges alone. PRs 1 to 7 change no behavior. PRs 8 to 11 do, and come after.
| PR | One concern | Behavior | Proof |
|---|---|---|---|
| 1 | The policy module and named profiles for today's windows. No callers. | No change | Table test: one row per rule above, today's answer pinned, oddities included |
| 2 | One shared clock reader; the six copies call it | No change | Same table test through each copy, plus daylight-saving edge cases |
| 3 | Renewals onto the policy | No change | Pinned per caller: renewal.ts calls, auto-start, bypass flag |
| 4 | Waiting-message holds (prospect, tour, collections and vendor chasers). Delete the old wait calculator | No change | Pinned hold times across 24 hours, each zone |
| 5 | One-off sends (voice callback, application send, re-engagement display) | No change | Pinned per caller |
| 6 | Held texts and the two collections checks | No change | Pinned, including the "no clock" refusal |
| 7 | Maintenance. Emergency stays an input, same values | No change | Pinned per handyman window, emergency passes |
| 8–11 | The four behavior changes below, one PR each | Changes | Before and after test per PR, plus replay of recent real sends |
| 12 | Build check: no hour logic outside the policy module. Deletes the old exports | No change | Planted violation fails the check |
| Later | Named exceptions and a dispatcher backstop | Not approved | — |
The "pinned" proof: each caller's test records today's answer for every hour of the day in each zone first, then must pass unchanged after the move. A deliberately wrong policy must turn it red.
| PR | Before | After |
|---|---|---|
| 8 | Self-guided staff notes send at any hour (temporary, Fede 10/06). Before that: dropped 9 PM–7 AM | Held until 8 AM, sent 8 AM–9 PM |
| 9 | Handyman default quiet 9 PM–7 AM | Window 8 AM–9 PM (emergencies unchanged until exceptions exist) |
| 10 | Mass text with "now" at night sends anyway | Held until 8 AM |
| 11 | Building clock for prospect, renewal and voice sends | Recipient clock from area code, building zone as fallback |
PR 11 also reverses the 2026-08-25 collections ruling that moved collections to the building clock.
TCPA covers solicitations, so internal notes are not legally bound.
Only matters when the recipient's area code also fails.
The code only has a Colorado rule set (collections). Western Slope is Colorado. I could not confirm the states for JP&Co or Situs from the code, so those are unverified.
src/lib/domain/compliance/quiet-hours/policy.ts next to recipient-clock.ts (contactWindowInTimeZone, area-code lookup, no fallback).temporal/activities/quiet-hours.ts, maintenance/quiet-hours.ts, self-guided-text-leaf.ts, mass-comms/send-time.ts, recipient-clock.ts, prospect-outreach.ts.temporal/activities/renewal.ts, *ForProperty variants, bypassQuietHours.prospect-outreach.ts resolveOutreachQuietHoldActivity, msUntilNextWindowOpen.leasing/reengagement-view.ts.escalation/held-message-rule.ts, bake-gate.ts, held-text.ts, vendors/dial-approved-session.ts, collections/approved-dun-send*.ts, compliance/collections/assess.ts (~line 393).maintenance/quiet-hours.ts, handyman-window.ts, temporal/activities/maintenance-comms.ts.self-guided-text-leaf.ts, self-guided-leave.ts, self-guided-arrival.ts.DEFAULT_HANDYMAN_QUIET_HOURS. PR 10: mass-comms/send-time.ts, mass-sends new page.tools-leasing.ts, tour/policy.ts. Separate bug: handle-propose-schedule.ts uses server-local getHours().This page is a planning aid, not legal advice. A lawyer should confirm the TCPA window and any state rules before they ship.