Deterministic-first inbound email is live in production

ADR-0114 deployment report & evidence — 2026-07-25, overnight autonomous run. Companion to the design brief and ADR-0114.

Verdict: deployed, no regression, better on every measured axis. All six PRs merged and live (prod commit 0e0e69ca3). Reply latency is equal-or-faster on identical prod benchmarks, decision correctness improved on 250 real emails and on the classifier eval, and silent drops are structurally gone — every formerly-invisible skip now lands as a recorded, reviewable state. Clara's reply behavior (answer the question + invite to a tour) is untouched.

The four confidence axes, A vs B

AxisOld pipeline (A)New pipeline (B)
End-to-end reply latency (prod bench, identical 24-scenario suite, The Willows) 57–62s avg reply (4 historical full runs); 54s on the clean 2-scenario run 54s avg reply (24 scenarios, fully-deployed pipeline) equal-or-faster
Decision-stage latency (harness, real timed calls, interleaved arms) 1436.6ms mean / 1663.4ms median / 2564.8ms p90 · 1.36 LLM calls per email 617.3ms / 0.1ms / 1793.8ms · 0.34 calls per email · 66% of mail decides with zero LLM latency (vs 32%) −819ms mean
Decision correctness (250-email real replay corpus) 91.2% / 92.4% 96.0% (240/250) +4.8pts
Drop-safety (LLM-outage simulation, 19 leads in window) 14/19 leads silently dropped 0 dropped — deterministic routes unaffected; ambiguous mail parks in needs_review structural

Latency: the real baseline, then the proof

You asked to verify "we were answering in ~30 seconds on average." The claim doesn't hold — the true baseline was ~47 seconds. Measured mailbox-arrival → reply-dispatched on all 45 real prospect emails Clara answered at Camellia in the last 30 days (Microsoft Graph server clock joined to ingestion rows, 54/54 join match, zero loss):

MeanMedianp90Under 30s
Real prospect traffic, old pipeline (N=45)46.7s46.9s60.9s2 of 45

Decomposition: ~8.3s mail-arrival→pickup, ~22s pre-reply triage/classification/lead-extraction, ~16s reply loop. ADR-0114 attacks the middle chunk. Two measurement notes: the receivedAt field on ingestion rows is Lambda-start time, not mail arrival — measuring from rows alone undercounts by ~8s; and one email on Jul 24 sat 56 minutes in a Graph webhook stall (excluded as an outlier, but it's a real ~2% tail — the retry work in PR #4504 is aimed at exactly that class).

Post-deploy, the identical 24-scenario production benchmark (real emails through the live SQS→Lambda→Clara→send pipeline) averaged 54s reply time vs 57–62s on the same suite against the old pipeline — and the harness decision-stage measurement explains why: two-thirds of mail no longer waits on a classifier call at all. Nothing was added to any path; the reply-generation stage is byte-identical.

Correctness: replay corpus + classifier eval

Drop-safety: what production shows since deploy

What shipped (all merged 2026-07-25, ~9:30pm–10:15pm MT)

PRChangeVerified by
#4503Quote-only / forwarded emails never return empty from the stripper; tour-date and lead-parse guards see "no fresh content" and behave exactly as before (no booking off quoted headers)fail-before/pass-after probes run against both HEADs; 856+ tests
#4504Graph fetch failures retry with backoff (Retry-After honored); on give-up a failed ingestion row is written so the redrive tool can recover the emailreverting the fix fails 5/6 new tests; route-level 202 isolation pinned
#4506AppFolio Received timestamps parsed in the property's timezone — the apply-direct incident's "6-hour lag" was UTC-vs-MDT skew; the real ingest took ~2 minutesmachine-independent two-zone regression test; DST hour pinned
#4507Prospect timeline splits "Received in AppFolio" (property-TZ, applicant's own source answer quoted as theirs) from "Synced to PropFlow"; Clara-born prospects unchangedpreview screenshots + post-deploy prod QA pass (PASS, both states)
#4505Apply-direct engagement seam wired but inert — application mints hit outreach stop-conditions by design; guest-card auto-enroll deliberately excluded per ADR-0094 (the claude-bot caught a reconciler backdoor that would have violated it; fixed + regression-pinned before merge)bot re-review: "blocking finding resolved"; provenance gate test
#4514ADR-0114 core: closed category union, exhaustive action map (compile error on missing member), needs_review never-drop, DB-derived classifier vocabulary deleted, decision counting + rate alerteval 30/30 vs 20/30; 1026-test email sweep; two independent re-reviews

Also shipped alongside: the fleet's review round removed two pieces of pre-existing PII from test fixtures. Blast-radius audit (read-only, three query paths agreeing): 7 AppFolio-minted prospects exist in all of prod, 6 never engaged, 0/4 app-minted engaged.

Decisions waiting on you

1. Widen allowedClassifications so general prospect questions get answered?

Every unanswered email in the benchmark — and in production before this deploy — is a real prospect question ("what's available?") skipped because the mailbox allowlist doesn't include general. That was true before the redesign too; the difference is it's now visible in the review queue instead of silent. Widening it is a one-line change but expands Clara's reply autonomy, so it's your call. This is likely the single biggest remaining lead-response win.

2. Applicant-acknowledgment message class (Trello 2mHQcDEf)

Apply-direct leads (0/4 ever engaged, incl. two already approved) still get no outreach — by design, until you define what Clara sends an applicant and its consent posture. The seam is wired and gated; flipping it on is a product decision, not an engineering task.

3. Guest-card auto-enroll — amend ADR-0094 or keep per-prospect PM arming? (Trello vgIt85Zp)

4. Backfill historical AppFolio timestamps?

The timezone fix is forward-only. Existing rows keep skewed stamps, so historical response-time metrics mix epochs and old prospect pages show a phantom ~6h receive→sync gap. A backfill is a prod data write — held for you.

5. needs_review needs an owner and an SLA.

Rows are persisted and projected but rendered nowhere yet. Without an operator surface it becomes a visible-but-stale sink — the ADR flags this as the condition for the never-drop guarantee to mean something.

Honest caveats & disclosures

Sources: prod DynamoDB propflow-prod (read-only queries cited in the run log) · Microsoft Graph receivedDateTime join (54/54) · ingest-harness results/latency/ + results/eval-classify/ (local worktree) · email-stress reports STRESS-1784948033603 (clean pre-deploy), STRESS-1784948715271 (mid-deploy), STRESS-1784960025209 (post-deploy) · PRs #4503–#4507, #4514 · session da2d4127 (agents-001).

PropFlow Docs