TL;DRThe plumbing is real and deployed. The product you asked for — reply arrives, draft waits in the same Missive thread — is not built yet. Two automations you rely on are silently dead, and one root cause explains almost all the messy data.
Seven independent review lanes read the live Notion, Missive and Instantly accounts (read-only), the deployed Modal code, 400-row fresh samples of both databases, and your last 14 days of instructions (132 verbatim prompts). What runs today: a reply lands in Missive, a webhook logs it to Notion and shows a card on the dashboard plus a Slack ping. What does not exist: the same-thread Missive draft, the unread guarantee, the AI note, the CRM link, the log-phone-call link, and any real do-not-contact list. Meanwhile the nightly repair job has been dead ~16 days (one alert, then silence) and the Instantly reply gate died on Aug 21 with no alarm at all.
Why this mattersDecisions needed: approve the repair order (fix suppression + wrong-contact matching + silent-loss bug first), approve one Activity-Log writer with a real event ID, and only then build the Missive draft experience — as a comment + draft, replacing the Slack ping and dashboard card instead of adding a fifth surface.
flowchart LR
A["Reply arrives<br/>in Missive"] --> B["Logged to Notion<br/>+ Slack + dashboard"]
B -.->|"planned, unbuilt"| C["Draft in the<br/>same thread"]
B --> D["You answer from<br/>the dashboard today"]
1 · What is proven to work
TL;DRDeployed code identity confirmed; 3,580 tests green; three of five crons alive.
Production is Modal app v64, and its code is byte-identical to the repo we reviewed — so what we read is what runs. The webhook spine logs replies, matches contacts by email, updates the CRM, and never sends anything by itself (your AI-never-sends rule is genuinely enforced). The prior audit's '85 tests passed' was wrong: the real suite is 3,580 tests, all green.
Why it mattersYou can trust the skeleton. Investment should go into the experience layer and the safety gaps, not a rebuild.
2 · The three P0 safety gaps
TL;DRNo unsubscribe protection · wrong-contact matching · a bug that loses history silently.
(a) When someone unsubscribes, you get a Slack ping and nothing is recorded anywhere — nothing stops the next campaign from mailing them again. Only two names are protected, hard-coded, on one code path. (b) When two CRM contacts share an email (three such pairs exist right now), the system silently picks the first — activity can land on the wrong person. (c) A reply can be marked 'already processed' before it is actually saved; if the save fails, the event is lost forever and both repair loops are blind to it.
Why it mattersThese are the only items with legal/reputation exposure. They are each a small fix and come first in the remediation plan.
3 · One dead automation, one that only looks dead
TL;DRThe nightly repair job is not dead — it runs every night and is killed by its own 50-minute timeout before it can report in. The Instantly reply gate really is dead since Aug 21, with no alarm.
Correction · 2026-08-31The original text below said morning_report had been dead for ~16 days. A live check on 2026-08-31 showed that is wrong. The job fires every night at 01:00 UTC, completes its reconciliation leg, runs the AI next-step pass and writes rows — the last batch landed 19 hours before the check (Notion rows timestamped 2026-08-30 01:11Z–01:48Z). What it never reaches is its final line, the one that stamps liveness. It is killed at its own 50-minute timeout roughly two minutes earlier. So the health panel reports "stale" while the job in fact ran. The lost work is only the tail: the Monday snapshot, the Friday learning review, and the stamp itself. The fix is to raise the timeout or batch the AI pass, and to stamp liveness per leg instead of once at the end — a much smaller change than "revive a dead cron". reply-ops is separately confirmed inert (0 tasks, empty log stream), so that half stands.
Original text, superseded: morning_report — the only job that reconciles missed replies and runs the AI next-step pass — last completed around Aug 15. The watchdog noticed once, pinged Slack on Aug 18, and by design never repeats. The reply-ops service (Instantly → Slack) stopped polling Aug 21; it lives in the wrong Modal workspace, from an unmerged commit, with no heartbeat.
Why it mattersA job that runs but is truncated looks identical to a job that is dead, because liveness is stamped only on the last line. Four independent reviewers read this wrong. Fix the liveness signal itself — stamp per leg, make the watchdog re-ring daily — before adding any new automation, or the next one inherits the same blind spot.
4 · One root cause behind the messy data
TL;DR17 different pieces of code write the Activity Log; none stamp an event ID.
90% of recent Activity rows have no identity fields — not corruption, just an unimplemented design: no deployed writer fills them. Page bodies are empty on every automated row (your 'email copy or transcript in the body' requirement). 'When' equals ingestion time. Fix is one shared writer that stamps an Event ID and checks for duplicates before creating — then the blanks stop growing and duplicates become impossible.
Why it mattersOne medium change fixes the data quality trend permanently; backfilling before that would just re-blank.
5 · Recommended path
TL;DRVariant B (provider-native) reached via Variant A repairs; three Notion views today, zero code.
Three ready-to-click weekly Notion views immediately surface 8 signed-but-lost contracts, 85 unidentifiable contacts and 23 stale work items. Then: suppression, exact-match, single writer. Then the Missive experience — draft plus an AI comment (not text inside the draft you must delete every time), only for strong leads, and retire the Slack ping + dashboard card in the same change. No new control panel, no new custom service.
Why it mattersShortest route to the system you described, with the least new software.
TL;DRWhat we could not verify, stated as unknown — not as passing.
Fireflies API was rate-limited for the whole review window (retry probe documented). Missive's API cannot show rules, drafts, or read/unread state — so 'the email stays unread' is unprovable from outside and needs one supervised live test. Instantly's blocklist contents are unreadable. The devbox running Proposal Studio was unreachable from this sandbox. Whether webhook-signature secrets are set in production was deliberately not checked (it would mean reading secrets) — it is a 2-minute manual check in the Modal dashboard and is listed first in Phase 0.
Closed since · 2026-08-31Slack signing secret: checked live and it is set in production, so the Slack webhook routes do verify signatures — the fail-open worry is refuted for Slack. Checked without reading any secret value, using the production preflight endpoint, which returns only the names of missing keys. Nothing was changed. Two neighbours stay open: STRICT_WEBHOOK_SIGS is unset (so a future rotation landing an empty value would silently revert to accepting everything), and the DocuSeal webhook secret appears to be absent, which would leave that route fail-open. The credential on origin/main: it is a truncated presigned URL from Perplexity’s file-upload bucket, pasted into a chat log. Not our AWS account, missing its expiry parameter entirely, and 34 days old. Non-exploitable. Left in history, as decided. Fireflies: the rate limit lifts at 2026-08-31 00:00Z; the recovery probe is now runnable.
Why it mattersThe rest stay open with exact recovery steps in COMPLETION_RECEIPT.md; none were converted into a pass.