Implementation Plan: Integrations — Twenty CRM
Feature ID: integrations-twenty-crm
Spec: ./spec.md
Tasks: ./tasks.md
Status: Done (retrospective — surface already shipped)
Last updated: 2026-05-08
1. Architecture Summary
2. Tech Choices
| Concern | Choice | Rationale |
|---|---|---|
| Module scope | @Global() forRoot() / forRootAsync() so any consumer can inject(ClientService) without re-importing the module | Matches MailModule and other Nest globals in the API. |
| HTTP client | @nestjs/axios (HttpService.request() + firstValueFrom) | Drop-in for the axios instance with Nest's RxJS-friendly wrapper. |
| Config | @nestjs/config's ConfigService.get<T>(...) with explicit env-var keys | Same pattern as apps/api/src/config/constants.ts — keeps env contract grep-able. |
| Auth gate | Per-class @UseGuards(AuthSessionGuard) (only on CompaniesController today — see OQ-1) | Reuses the platform-wide session guard. Class-level gate is cheaper than method-level. |
| Config gate | CrmSyncGuard reading CrmConfigService.isEnabled + validateConfig | Lets the integration self-disable when env vars are missing without surfacing a 500. |
| Retry strategy | RetryUtils.withRetry — exponential back-off, jittered delay, last-error re-throw | Twenty CRM rate-limits at 429 and stresses on 5xx; jitter avoids thundering-herd retries across replicas. |
| Tenant addressing | CrmTenantService — tenantId = work_<workId> / globalTenantId / 'global_everworks' | Future-proofing: today's single-workspace deployments use global_everworks; per-work tenants are a forward-compatible hook. |
| Mapping utility | class MappingUtils with static methods | Pure functions, no DI required. Same pattern as RetryUtils. |
| Schema fetching | TwentyCrmService.makeRequest(..., schema=true) swaps /rest → /rest/metadata | Lets metadata-discovery callers (e.g. tooling, future schema-driven sync) reuse the same auth + headers. |
| Error wrapping | HttpException({message, details}, statusCode) for upstream errors; HttpException('Failed to communicate ...', 503) for transport | Lets Nest's exception filter render OpenAPI-shaped errors back to the client without leaking the upstream stack. |