Skip to main content
← Back to list
01Issue
FeatureShippedSwamp CLIPublic
Assigneeskeeb

Relationships

↔ sibling #809

#1665 Sync every signup to Attio, and backfill the existing population (#809 follow-up)

Opened by keeb · 8/15/2026· Shipped 8/15/2026

Follow-up to #809, which shipped the Person link but not the population it was asked for.

#809 asked that "every signup should land in Attio as a product User (linked to a Person), and all existing users should be backfilled." What shipped in #1088 was the missing users.person linkage — real, and it closed a measured gap (person fill rate was 0 across every synced user record) — but only for the collectives the sync already admits.

The gap, measured against prod

count
Prod operatives 561
Collectives 608
…with a trial started 10
…with a subscription 11
Operatives inside those (what reaches Attio today) 22
Operatives with an Attio Person already 46
Operatives with no CRM presence at all 515

The CRM's contacts and the product's users are today two nearly-disjoint populations: Attio holds 270 distinct addresses, and only 46 of them belong to an operative. Those 270 came from outreach work, not from this sync.

What is missing

  1. Scope. isInScope (lib/app/attio-desired-state.ts) admits a collective only on trialStartedAt or a subscription document. An ordinary signup starts no trial — the personal collective is provisioned by provision-operative.ts, which never goes through createCollective — so a free operative never reaches Attio at all.
  2. The two publish points #809 named. enqueueAttioSync has exactly four callers today: collective-trial.ts, trial-ended-watch.ts, admin/extend-collective-trial.ts, billing/sync-subscription.ts. There is no publish from databaseHooks.user.create.after (signup) and none from emailVerification.onEmailVerification.
  3. The backfill. No lib/app/backfill-attio-users.ts, and no marker-guarded enqueue over the existing population. The hourly stage sweep only walks collectives already in scope, so it does not serve as one.

What this would cost

Widening to every operative takes the people object from ~270 addresses to ~785 — 515 creates — and puts the entire free user base into a paid third-party CRM. That is the trade #809 asked for and that the #1088 scope decision deliberately declined; Resend already owns every-account contact sync, and Attio has been treated as a sales CRM. This issue exists to make that a decision someone takes deliberately, rather than an absence nobody notices.

If the answer is "yes, but not all of it", the obvious middle grounds are: operatives with a linked account, operatives with non-zero activity, or Person-only (contact grain) for everyone while keeping the User+Workspace grain commercial.

Three things the implementer must not rediscover

  • CRM_MAPPING_VERSION (in lib/repositories/crm-records.ts) has to be bumped if the payload shape moves. processAttioSync short-circuits on a fingerprint of the desired state, which cannot see a transport-side change; without a bump the work is invisible in production while every test passes. It also does not trigger a re-enqueue on its own — a repaint rides the sweep's 24h staleness window.
  • A Person is resolved by lookup-then-create, never asserted. Attio's PUT overwrites a multiselect, and email_addresses is multi-valued — six real people in the workspace hold a curated personal-plus-work pair that an assert would strip. At full scope this matters more, not less: of the 22 currently synced, 5 already link to an existing Person rather than create one.
  • Volume. Writes are capped at 25/s and shared with the humans using the workspace, and every write lands in the activity feed. A backfill of 515 needs the same drip discipline the queue already gives; the fingerprint memo keeps the steady state silent afterwards.

Acceptance

  • A new signup appears in Attio without a trial or a subscription.
  • Email verification flips the verified attribute on the same record.
  • The existing population lands via a marker-guarded backfill, not a one-shot script.
  • Re-running any of it writes nothing the second time.
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 13 MOREREVIEW+ 6 MOREPR_MERGED+ 1 MORENOTIFICATION_SKIPPED

Shipped

8/15/2026, 3:10:36 AM

Click a lifecycle step above to view its details.

03Sludge Pulse
keeb assigned keeb8/15/2026, 1:33:16 AM
keeb linked related to #8098/15/2026, 1:20:59 AM

Sign in to post a ripple.