Saltar al contenido principal

ADR-009 — CRM via Twenty (sync connector), not a native build

:::note Contenido en inglés Esta página del wiki se sincroniza desde la base de conocimiento en inglés y todavía no está traducida. :::

reference · backend · updated 2026-06-23 · source

  • Status: Accepted
  • Date: 2026-06-23
  • Owner: Backend (José)
  • Related issue: #741 (depends on ADR-007 Layer-2 ingestion; supersedes the native leads screen)

Context

  • The 9-screen Comprender effort (#727) surfaced that leads — the only true CRM screen — had no @tedos/shared/api contract, unlike the other sections. We had to decide build a native CRM vs integrate an OSS CRM.
  • TED OS is a white-label solution-builder; "absorb an external OSS tool cleanly" is itself a capability worth proving (the integrate thesis), and CRM is broad — a native build would chase a moving target (pipelines, activities, automation, dedup, reporting).
  • A platform CRM does not cover Comprender's full domain: Mexican fiscal invoicing (CFDI) and courses/LMS stay ours regardless.

Decision

  • Headline: Integrate Twenty (OSS CRM) as the CRM system-of-record via a sync connector; the native leads screen is dismissed.
  • Operators do CRM in Twenty's own UI; a tenant connector keeps our Postgres in sync so a CRM contact unifies with their student identities through the existing dedupe path.
  • Twenty contacts land on a new person identity channel crm via the ADR-007 Layer-2 ingestion seam (ingestRecord / ingestViaConnector) — upsertAndLink does the merge.
  • Connection = sync (API key + webhooks), chosen after validation (below). Not embed/SSO.
  • Scope boundaries: this does NOT white-label Twenty's UI (AGPL + maintenance), does NOT replace payments/CFDI or courses, and does NOT add crm to the Resumen's peopleByChannel (that stays acquisition-channel only: live/recorded/posgrado).

Validation findings (Twenty, June 2026)

DimensionFindingConsequence
LicenseAGPL-3.0Unmodified use (API) is clean; white-labeling its UI would trigger AGPL → avoid.
Self-hostDocker + Postgres + Redis, ~$20–50 VPSRun per client; cheap.
Multi-tenantIS_MULTIWORKSPACE_ENABLED=true + wildcard DNS → one workspace per clientFits white-label.
APIFull REST + GraphQL, API-key auth, webhooksThe integration surface.
SSO/ClerkGoogle + Microsoft + email/pw; no SAML/OIDC (paid Org tier / Authentik proxy)No clean Clerk session → embed/SSO rejected.
White-labelNone documentedAn embed would look like Twenty, not us → another reason to sync.
CFDI / coursesPure CRM — neitherWe keep payments/CFDI + LMS.

Consequences

Positive

  • Proves the integrate thesis; we maintain ~no CRM UI. The contract/seam is the swap point — any other OSS CRM (or a future native build) implements the same IngestRecord flow without UI change.
  • Lead↔student unification is free (same person + dedupe), giving a real conversion view.
  • AGPL-clean: we call Twenty, never modify it.

Negative / trade-offs

  • Operators use a second, Twenty-branded tool (no shared Clerk session today — Twenty has no OIDC).
  • Sync is one-way (Twenty → us) for v1; writing back to Twenty is out of scope.
  • Twenty's exact webhook signature scheme + REST people shape are confirmed against a live workspace in the ops follow-up (built defensively meanwhile).

Follow-ups required

  • Ops (effort #741 sub-issue): self-host a client Twenty workspace, wire per-client API key + webhook secret + base URL (credentials.twenty), run a backfill, validate end-to-end live.
  • Scheduled pg-boss backfill job (uses fetchTwentyPeople from @tedos/engine).
  • Per-tenant webhook secrets via the credential store (v1 uses one TWENTY_WEBHOOK_SECRET env).

Alternatives considered

OptionWhy not
Build a native CRMChases a broad moving target; the contact backbone (person) already exists, but pipelines/activities/automation/reporting are weeks of work better bought.
Embed Twenty via Clerk SSOBlocked: Twenty has no native SAML/OIDC (paid tier / Authentik proxy), no UI white-label; the embed wouldn't share our session or look like us.
API-behind-our-seam (our UI on Twenty's API)Contradicts dismissing the native leads UI; couples our UI to Twenty's schema/uptime.

References