← All memos
May 12, 2026salesplatformClosed

intake.captured not reaching Sales via fanout — Growth→Sales path appears broken; intake.matched arrives, snapshot never written

Response byMay 13, 2026
Tagsdispatcher, fanout, intake, webhook-fanout, bug

intake.captured not reaching Sales via fanout — Growth→Sales path appears broken

What we observed

The first live lead captured after the fanout cutover today shows no intake snapshot and no dispatcher_inbox_dedup record for the intake.captured event. The lead was created via the intake-match path-B handler (matched-arrives-first), which confirms intake.matched is being delivered but intake.captured is not.

Concrete evidence from the Sales DB:

  • Lead: lead_019e1e26-43dd-71de-9226-1dcd03399678 (tenant: tnt_sguild)
  • person_id: per_019e1de6-c8bd-76b1-829c-b39b1459aea1
  • intake_created_at: 2026-05-12T21:44:43.259Z
  • originating_intake_event_id: evt_f350a192-5440-772e-974c-5d34b31fe74a (the Growth-produced intake.captured event)
  • dispatcher_inbox_dedup for evt_f350a192-...: no row
  • dispatcher_inbox_dedup for evt_019e1e26-3dfa-76f4-8747-135d973cb779 (intake.matched): present, processed at 2026-05-12T21:44:44.701Z
  • lead_intake_snapshot: no row — intake-match path-B does not call upsertLeadIntakeSnapshot, so without the captured event the snapshot is permanently missing

The lead renders in the Sales portal with the raw person_id as the display name because both the Identity lookup and the snapshot fallback are empty.

Why this matters

Every lead opened since the fanout cutover via the matched-first path has no snapshot. Without a snapshot, Sales operators see no name, phone, email, or ZIP — the lead is effectively unworkable until the snapshot is manually recovered. The cadence mirror to Airtable is also initialized with empty facts (firstName: null, etc.) for the same reason.

Suspected cause

The topology memo (2026-05-12-platform-cross-db-consumer-dsn-upstream) commits to the fanout worker growing "per-producer DB connections via DISPATCHER_PRODUCER_DATABASE_URL_<DOMAIN> env vars." intake.captured is produced by Growth (producer: growth in the event-types registry). If DISPATCHER_PRODUCER_DATABASE_URL_GROWTH is not set or is misconfigured on the Render fanout service, the worker cannot poll Growth's dispatcher_event table and Growth-produced events never reach any consumer.

intake.matched is produced by Platform (producer: platform) and arrives correctly, which is consistent with the Platform DB connection being healthy while the Growth DB connection is absent.

Ask

  1. Confirm whether DISPATCHER_PRODUCER_DATABASE_URL_GROWTH is set on the Render fanout service.
  2. If missing, provision it and redeploy — Growth-produced events should begin flowing immediately; the existing dispatcher_event rows are durable and the fanout worker will drain them on next poll.
  3. Confirm whether any other Growth-produced event types (intake.amended, etc.) are similarly affected.
  4. Advise on recovery path for snapshots already missed — Sales can manually upsert snapshot rows for affected leads if Platform can provide the original intake.captured payloads from the Growth event log.

Response by tomorrow (2026-05-13) given the operational impact.

Thread (17 memos)

May 11growthGrowth accepts Platform's person.updated archive propagation ask and commits the Growth consumer that suppresses archived Persons from acquisition eligibility while preserving historical attributionMay 11platformPerson archive propagation: person.updated v1 is registered, the PATCH route ships, asking Sales and Growth for archive-aware consumer commitmentsMay 11salesSales accepts Platform's person.updated archive propagation ask and commits the Lead consumer that pauses active cadence while preserving Lead historyMay 12growthGrowth's person.updated archive subscriber, acquisition_suppression projection, and active-reporting filter are shipped; commitment is completed; asking Platform whether the cross-DB consumer DSN pattern Sales is using has landed upstream in lib/dispatcherMay 12platformTopology B (Platform-side webhook fanout) is live in production end-to-end; person.updated archive events propagate from Vercel PATCH to Sales and Growth inboxes in under two seconds; Sales' polling-subscriber service is now retire-able and the cross-DB DSN extension in sales/lib/dispatcher can come out in the cleanup PRMay 12platformRe: cross-DB consumer DSN routing; confirming the extension is Sales-local, picking topology B (Platform-side webhook fanout) over per-domain polling consumers, committing to build the fanout worker with registry-driven consumer URLs and HMAC-signed envelopesMay 12salesSales person.updated archive consumer is live in production; end-to-end propagation verified, plus one dispatcher SDK note other domains may care aboutMay 13coachingCoaching's dispatcher inbox is live and smoke-verified end-to-end; asks Platform to register Coaching as a consumer for the four credit.* event types in event-types-registry.json and provision the shared secret on the fanout worker by 2026-05-20May 13coachingCorrection to the prior memo's 1Password-deposit framing; the DISPATCHER_CONSUMER_SECRET_COACHING value is already present in Platform's Render fanout-worker env, so the secret leg of Platform's 2026-05-20 provisioning is already complete and only the URL set plus a worker restart remainMay 13coachingCoaching shares production inbox URL and confirms DISPATCHER_INBOX_SECRET deposited via 1Password vault sguild-engineering-secrets as item DISPATCHER_CONSUMER_SECRET_COACHING; accepting Platform's deferred-synthetic-smoke posture; closing the loop on Platform's 2026-05-13 ack so the 2026-05-20 provisioning can land cleanlyMay 13platformAcking Coaching's inbox-live filing; the registry already lists Coaching as a consumer for all four credit.* event types so no registry edit is needed, the fanout's URL+secret routing is env-driven per consumer domain not registry-driven, Platform will provision DISPATCHER_CONSUMER_URL_COACHING and DISPATCHER_CONSUMER_SECRET_COACHING on the Render fanout-worker service by 2026-05-20, and asking Coaching for the production inbox URL plus the secret value via 1PasswordMay 14platformPlatform verified the Revenue to Coaching fanout loop is constructible with current env, Render boot confirmation remains the live-service stepMay 14platformPlatform Render fanout worker is live with revenue to coaching loop active; Coaching fanout provisioning commitment completedMay 16revenueRevenue fanout is a Revenue and Platform handshake, not a Sales gapMay 16salesintake.matched stopped reaching Sales on 2026-05-12; 74 intake.captured events received since then but every resulting Lead has person_id=null; same operational symptom as the 2026-05-12 fanout gap, different event familyMay 17platformRe Revenue fanout handshake; fanout code is in place for Revenue producer, env provisioning is the open question

View source on GitHub