0062 — Prospect Outreach Cadence: Production Activation & End-to-End Test (Willows)


Context

ADR-0061 shipped the prospect outreach cadence disabled behind a two-factor fail-closed gate: the prospectOutreachWorkflow, its activities, the propflow-leasing(+-eval) queues, the autonomous gate, and the prospect-page Re-engagement card (live, renders for any inquiry that has cadence state). Phase-1 review (PR #2470) surfaced three gaps that block an actual run:

  1. No production trigger — nothing in prod calls startProspectOutreachWorkflow; the cadence can't begin on its own.
  2. No inbound-reply bridge — nothing calls signalProspectReplied, so a prospect's reply wouldn't pause the cadence.
  3. The leasing queues' polling status is unverified. Push-to-main auto-deploys the worker to prod (see current state), so merging ADR-0061 (#2470) should have rolled propflow-leasing(+-eval) onto the prod worker — but that the workers are actually polling those queues in Temporal Cloud has not been confirmed.

This ADR specifies the architecture to activate the cadence in production and to run one controlled end-to-end test at The Willows (appfolio-45, the bench property; inbox propflowaicalendar@outlook.com).


Temporal — current state (as of 2026-06-18)


Decision (proposed high-level architecture)

D1 — Production trigger: event-driven at claraEngagedAt

When Clara stamps claraEngagedAt on a prospect's first reply (the existing leasing reply path — conversation-manager / tools-leasing), fire startProspectOutreachWorkflow via signalWithStart (idempotent, USE_EXISTING), fire-and-forget. Gated by isProspectOutreachAllowed(propertyId). (A firstReplyFollowUpSentAt migration guard originally also skipped previously-nudged leads; it was removed 2026-07-09 with the first-reply-followup decommission — see ADR-0061 — because it tombstoned legitimate new leads. outreachCadenceAnchorIso presence is now the sole start-idempotency guard.) Pass anchorIso = claraEngagedAt so workflow timers and the UI's "next send" agree. One shared trigger helper called from each engage site (email/voice/sms), not duplicated per path.

D2 — Inbound-reply bridge → signalProspectReplied

When an inbound message from a prospect with a running cadence is processed, call signalProspectReplied(prospectId, { repliedAt }); the workflow short-circuits and stamps outreachCadenceCompletedAt. STOP / opt-out routes through the same signal plus consent suppression. Dedup against the existing leasing reply handling (Open Q2).

D3 — Simulated inbound lead (the test)

Inject a Zillow-formatted inbound email to propflowaicalendar@outlook.com for appfolio-45 via the existing email-stress SQS transport (propflow-inbound.fifo). The existing leasing flow creates the prospect, Clara replies by email, and stamps claraEngagedAt → D1 fires the cadence. No new ingestion code.

D4 — Worker readiness (verify, don't redeploy)

Push-to-main already auto-deployed the worker to prod when #2470 merged, so the prod Fargate worker should poll propflow-leasing(+-eval). Verify the queues show active pollers in Temporal Cloud (propflow-renewal-prod.hmqma) before arming — don't assume. If testing on stage (D8), manually deploy the stage worker first (workflow_dispatch env=stage), since stage is not auto-deployed.

D8 — Test environment: stage-first if data permits, else prod-with-fake-lead

A stage stack exists (separate namespace propflow-renewal-stage.hmqma, propflow-stage table, stage.propflowai.co; Twilio/Graph shared with prod). Preference order:

  1. Stage — safest (no prod-data/send risk), but requires: (a) a manual stage worker deploy, and (b) provisioning appfolio-45 + a connected test inbox + a consent row in the propflow-stage table, which today likely only exist in propflow-prod. If that seed is cheap, stage is the right place to run the end-to-end.
  2. Prod with a fresh fake lead (D6) — if stage seeding is prohibitive. Arm appfolio-45 only, send to a controlled sink, disarm in a finally. Higher blast radius (real prod table + real provider sends), so it stays a deliberate, approved, one-shot run.

Decision deferred to the seed-cost check; both paths use the same gate, same activities, same UI card.

D5 — Gating & rollout

Two-factor, fail-closed. Production arming = set PROSPECT_OUTREACH_AUTONOMOUS_SENDING=armed on the worker env + Property.autonomousProspectOutreachEnabled=true for appfolio-45 only. Disarm = unset env or flip the flag → instant halt. Arming an autonomous-send flow in prod is a high-risk change requiring explicit per-property approval (CLAUDE.md). The compressed propflow-leasing-eval queue runs the demo in minutes. Follow-up for the D1/D2 code PR: add PROSPECT_OUTREACH_AUTONOMOUS_SENDING to CLAUDE.md's Temporal-worker safety env-var table (same format as RENEWAL_AUTONOMOUS_SENDING) so on-call has one authoritative place to look.

D6 — Test recipient strategy (no identity-persona risk)

Use a fresh fake lead (fake phone + fake email) so nothing attaches to a real Person/persona (the team member's real email and phone are separate identity records — combining them on one synthetic lead corrupts neither this way). For SMS, use a number the dispatcher's existing isTestSender synthetic-drop recognizes (+1555… / +100000…): the dispatch path logs the outbound and the cadence advances (so the UI card updates and is the verification source of truth) while the message is dropped before the real Twilio hop — no carrier delivery, no real device touched. (Twilio's magic test number +15005550006 is the alternative if we want a real provider round-trip without delivery.) Real personal-phone delivery is deferred (needs an identity merge or a per-test claim — Open Q3).

D7 — Verification & observability


Consequences

Open questions

  1. Trigger call site — exact shared insertion point for claraEngagedAt across email/voice/sms engage paths.
  2. Reply-bridge dedup — coexistence with existing leasing inbound handling so a reply isn't double-counted.
  3. Real personal-phone delivery — requires merging the tester's email+phone identities or a one-off claim; deferred to keep personas clean.
  4. 10DLC/TFVappfolio-45 uses the test TFN (TFV approved); real-volume SMS at a customer property needs the registration from ADR-0061 D10.

Rollback

Unset PROSPECT_OUTREACH_AUTONOMOUS_SENDING (or flip the property flag) → all sends stop immediately; in-flight workflows dry-run no-op. No data migration to undo.