Clara's Gatekeeper

Every message Clara sends passes one checkpoint first. Each module has a three-position switch, and the switch decides what happens to the message.

7 October 2026 Β· Every message Clara sends now goes through the switch

The switch

Off

Clara doesn't send for this module. Setting up works the same way while a customer finishes onboarding that module.

Supervised

Clara writes it. A person approves it before it goes out.

Live

Clara writes it and sends it on her own.

What happens to one message

Step 1Clara writes a text, email or call
β†’
Step 2The gatekeeper finds its module and building
β†’
Step 3It reads that building's switch
OffStopped. Nothing goes out.
SupervisedHeld and posted for approval. Sent once a person says yes.
LiveSent right away.

Supervised only holds messages. Updates to AppFolio, such as work orders, vendor assignments, notes, guest cards and tour bookings, always happen right away. Only what Clara says to a person waits.

The six modules

Tap a position on any card to see what it does. Each card opens on its starting setting.

Calls in Supervised

A call can't wait for approval, so a Supervised building picks one of two options.

πŸ“¨ Messages only (calls live)

Texts and emails wait for approval. Clara still answers and makes calls.

πŸ“ž Everything (calls to team)

Texts and emails wait for approval, and calls go to the team instead of Clara.

Always goes out, whatever the switch says
🚨

Safety emergencies

Anything that puts someone in danger.

πŸ”§

Urgent repairs

Work orders marked high or emergency priority.

πŸ™‹

Asks for a person

Escalations and requests that ask the team to act.

πŸ‘€

Started by staff

A message someone on the team sent or approved.

πŸ’¬

Replies to staff

When the team texts Clara, she answers right away.

❓

Everyday questions

A general reply like "what are your office hours?" If Clara files it under a module, that module's switch applies.

An approved message is the same as Live

Approving a message sends exactly what Live would have sent. The only difference is the wait for a person.

πŸ“Ž

Same parts

Same attachments (like the signed lease PDF), the same people copied, the same email thread.

πŸ—‚οΈ

Same follow-up

Same AppFolio notes, office alerts and delivery tracking as an automatic send.

πŸ•˜

Same timing

Door codes, staff notices and vendor messages go out any hour once approved; resident and prospect messages respect quiet hours.

πŸ›‘οΈ

Never stale

If the visit moved, the job closed, the address changed, or the resident wrote again, it doesn't go out, and the thread says why.

Rules for held messages
⏳

Expires after 24 hours

If nobody approves it within a day, it's closed and never sent.

πŸ”

Newest reply wins

A newer reply to the same person replaces the older one still waiting.

πŸŒ™

Waits out quiet hours

A message approved at night goes out in the morning. Urgent jobs don't wait.

πŸ›‘

Checked again before sending

If the module was switched Off after approval, or the job changed, it doesn't go out.

Where approvals happen
πŸ’¬

Slack

Each held message is posted to the building's Slack channel (#alerts if it has none) with the message and a link. A reminder follows in the thread while it waits.

βœ…

Approve or reject

The link opens the held message, where a person approves or rejects it.

⏸️ While it waits

One short card: who it's for, the message, why it's held, and "Reply approve or reject: why".

βœ… / ❌ When it's done

The thread ends with one line: "βœ… Sent 9:05 AM to … Β· View conversation", or "❌ Not sent β€” reason". A bounce after sending turns βœ… into ❌.

Tested on real messages (5 and 7 Oct 2026)

Run on PropFlow's own test buildings only. Each message was held, approved, then delivered to a test inbox or phone.

βœ… Prospect email reply

Held at a Leasing-Supervised building, approved, delivered.

βœ… Resident text routed to Maintenance

Clara called it general, but it was routed to Maintenance (Supervised), so it was held. Approved and delivered.

βœ… Staff heads-up

The leases-ending digest was held, approved and delivered.

πŸ›  Found and fixed

Dropped emails were marked "delivered", held replies showed early in the thread, and the 24-hour cleanup never ran. All three are fixed and live.

Final round (7 Oct 2026)

βœ… Bounced email

Marked "dropped", and the Slack thread shows one ❌ Not sent line.

βœ… New Slack cards

Compact ⏸️ card when held, then βœ… Sent with the time, who, and a conversation link.

βœ… Night-time approvals

An approved handyman page and team notice went out right away, at 12:40 AM.

βœ… Newer resident text

An approved reply that had gone stale was replaced by a fresh draft, and the team was told.

βœ… Emergency replies

A resident texted about an emergency at a Supervised building. Clara's reply reached their phone within seconds, with nothing held, including on the follow-up text. Emergency email replies use the same fix but haven't been tested live yet.

βœ… Handyman in AppFolio

The handyman is assigned in AppFolio as soon as the job is created, while his text still waits for approval. Re-tested live.

Where the switches live

Company page β†’ Modules card

One row per module: one status, one button. A module moves Off β†’ Onboarding β†’ Ready to turn on β†’ Supervised β†’ Live. A PropFlow engineer presses Turn on, and the module starts in Supervised.

Buildings table

Sets it per building. A building that follows the company shows "Supervised (from company)".

Only PropFlow staff can change Off / Supervised / Live. Customers see a simple on/off per building.

Preview leak audit Β· 30 October 2026

What happened. On 29 Oct at 19:11 UTC the Off-to-Preview rollout put Renewals and Collections in Preview at Western Slope and Situs. On 30 Oct at 14:30 UTC the daily renewal scan read Preview as Supervised, opened the monthly "review the list" hold, and the hold emailed the approval request to Western Slope's renewal reviewers, Jay and Kat. Delivered. Fede's rule the same morning: Supervised and Preview send nothing to anyone without an approval, and Preview never opens an approval request to customer staff.

StoredRenewals = preview
β†’
Read by old codeparses as drafts (Supervised)
β†’
Hold opensSlack card + review page
β†’
Hold's own sendsreviewer email, module 'none', no door

Preview is stored as its own word but most readers only know Off / Supervised / Live. Anything that narrows to those three treats Preview as Supervised. Anything a hold does after it is created (reviewer email, Slack card, approve button) ran with no mode check.

What actually left Western Slope under Preview (29 Oct 19:11 UTC β†’ 30 Oct 18:11 UTC)

Source checkedRenewals / Collections traffic foundVerdict
SendGrid (117 emails from Clara)1 email, "10 renewals to approve", to jay@ and kat@ (+ copy to our hello@)Leaked to customer staff
Slack #apartment-western-slope (49 bot posts; 4 members, all PropFlow)2 "Held for review" cards: the renewal list, and a Collections text draft to a residentInternal only
Twilio texts and calls from Clara's Western Slope number9 texts, all leasing or maintenance; 0 callsNothing
Western Slope Outlook mailbox sends5 sends, all leasingNothing
AppFolio write queue (28 writes) and texts through AppFolio (17)All leasingNothing
Holds created (25)2 stamped Preview: the renewal list (reviewer notices sent) and the Collections text (pending)Both now rejected
Renewal records, Collections records1 notice-to-vacate read in; 0 outbound; 0 collections rowsNothing

Controls held: the known email and the known hold came back in their queries. Temporal itself was not listed (no connection from the Mac); the renewal and send records stand in for it. No resident, prospect or AppFolio record was touched by either module.

Closed the same day

Western Slope

Renewals set back to Off (read back Off at all 10 listed buildings). October renewal list rejected. Pending Preview Collections text rejected.

Situs

Mode left in Preview (Fede: just Western). October renewal list rejected so November starts clean. No reviewers stored, so no email ever left.

Fix on main

#23805 (merged 17:12 UTC): the reviewer email asks the mode. Gap: it checks only the list's anchor building. Scan-under-Preview fix: Smith's session, open.

Code audit: where else Preview or Supervised can reach a person or AppFolio

Three auditors on origin/main 02a2004, read-only. Positive controls held (the batch scan and the old reviewer email were both flagged). About 60 send sites in 12 groups; three causes explain nearly all of them.

#CauseWhere (examples)ReachesSmallest fix
1What a hold does after it opens. The Slack card to the customer's channel, the bump reminder, and some approve routes run with no mode check. Approving a Preview email hold or a held AppFolio write still sends or writes.postHeldEmailForReview, postHeldTextForReview, bumpStaleHolds, bake-gate 'none' arms (:826, :1486), approve-held-pms-write, turnover-gate folding Preview into supervisedStaff, resident on approve, AppFolio on approveSkip the customer-channel card when heldInPreview; approve routes declare the real module and re-read the mode; writes check writesGo. Gera's #23823 covers the email approve.
2Staff notices declared 'none' or 'team_alert', which tells the email and text doors to skip the mode. ~35 sites.reply-release close/ack/retraction notes, gated-inbound team notice, promises pm-delivery, autonomous team note, forward-to-PM, tour confirmations/cancellations/host fan-out, renewal yes/executed/holdover office notices, renewal_escalate, PM action reminders, vendor outcome, move-out mismatch, staff welcome text, unknown-caller noteCustomer staff (email and text)Replace with sendOrHoldTeamNotice({ coworkerNoteOf: <module> }); staff texts declare module + recipientType:'staff' + draftApprovalId:null. Caller-message paths ask the one rule with CALLER_MESSAGE_SUBJECT.
3Gaps in the gates. decisionRequestModuleOf knows 3 modules; Resident services missing from the Supervised backstop; team-notify engine applies the mode only to email notices with a building and a module; new-lease and turnover gates have no Preview value; generic PMS writer has no Preview branch (guest-card and work-order writers do).decision-request-hold.ts, supervised-reply-rule.ts, team-notify/engine.ts:527, new-lease-gate.ts, writer-registry.ts:219, move-out draft hook, listing hooks, charge re-stageStaff texts, resident replies (Resident services), AppFolio writes (turnover, listings, charges)Fail closed on an unknown module; add Preview to each gate; recording writer at Preview in the generic registry.
4Slack asks to the customer's channel with no mode check.postOpsEvent action cards, new-lease review alert, fair-housing hold notify, reply-release :2551Staff (if the channel has customer members)One gate in postOpsEvent; emergency card exempt.
5Per-path override beats Preview by Gera's decision 3 (path-mode.ts:169-216): a path set to Live sends at a Preview building.twilio/client.ts:1240 pathRuled skips previewRefusesSend; process-envelope.ts:1739Anyone on that pathDecision for Fede and Gera, not a bug.

Already safe: every resident, prospect and vendor send that declares its real module and threads the approval (email door, text door, voice door), the AppFolio writers behind writesGo, the emergency rule, and human-authored sends. Full file:line tables and the per-area notes are in the session's audit files; Smith has the list on the bridge.

Decisions

D1 Β· Does Preview open an approval request at all?

Today's build: yes, by design (the hold lands on the customer's review page and Slack card, "approving will not send it"). Fede's 30 Oct rule: no. Recommendation: no. At Preview the hold's front door records and opens nothing reachable. One change in holdEscalationEmail / holdEscalationSms; the Slack card and approve routes then have nothing to act on.

D2 Β· Path override over Preview

Keep Gera's decision 3 (a path set Live sends at a Preview building) or make Preview absolute. Recommendation: Preview absolute; a per-path Live at a Preview building is the kind of hidden switch that bit us today.

One choke point Β· proposed 30 October 2026 (Fede's ask, handed to Gera's lane)

Root cause of the sprawl, in Fede's words: "too many gates sending out notifications, not a single choke point. We've tried this many times and it keeps sprawling." The audit above agrees: about 10 places read the mode and decide for themselves, and about 35 of 60 senders skip the doors by declaring module 'none' or 'team_alert'. Four sweeps in four weeks (gatekeeper 7 Oct, Supervised-holds-everything 26–29 Oct, Preview 28–30 Oct, path-mode audit 30 Oct) patched sites; none removed the ability to bypass.

Any sendermodule + record, always
β†’
One decisiondecide(module, building) β†’ record Β· hold Β· send
β†’
One sender per channelemail Β· text Β· call Β· customer Slack Β· PMS write
β†’
Consumersholds, approvals, cards, bumps act on the decision, never send on their own
RuleWhat it replaces
A send cannot be made without a module and a record. There is no 'none'.The 'none' / 'team_alert' escape hatch at ~35 sites.
The mode is decided once, in one function, from the stored mode (Off / Supervised / Live / Preview / path override). It returns record, hold or send.Ten readers each narrowing to their own three values.
Holds, approvals, Slack cards and bump reminders are consumers of that decision. Approving re-asks it.Hold-side sends with no mode check (today's incident).
Exemptions are a closed list in one file: emergency, caller left a message, a human wrote it.Exemptions scattered as flags and module names.
A build check refuses importing the raw senders (SendGrid, Twilio, voice, Slack notify, PMS writers) anywhere but the door module.Audits.

Migration order (each step dark, byte-identical replay, no switch)

1 Β· The decision

One function; the ten readers become callers. Positive control: the batch scan and the turnover gate change behaviour at Preview; nothing else does.

2 Β· Close the hatch

Type the send calls so module is required; migrate the ~35 'none' sites to their real module; delete 'none'. Hold-side sends go through the door.

3 Β· Lock it

Lint rule: raw senders importable only from the door. Exemption file. Delete the per-reader copies.

Owner: Gera's lane (modules, paths, gatekeeper), with the incident lane's audit tables as the inventory. Decisions D1 and D2 above are inputs. Status: proposed, handed off 30 Oct via the engineering bridge.

PropFlow Docs