Skip to main content

Integrations Module

The Integrations module provides external CRM connectivity for the Ever Works platform. Currently, it implements a full integration with Twenty CRM, an open-source CRM system. The module is located at apps/api/src/integrations/.

Architecture Overview

IntegrationsModule
└── TwentyCrmModule (global, dynamic)
├── Config
│ └── CrmConfigService -- Environment-based config
├── Services
│ ├── TwentyCrmService -- HTTP client for Twenty API
│ ├── ClientService -- High-level CRUD operations
│ └── CrmTenantService -- Multi-tenant context resolution
├── Controllers
│ ├── CompaniesController -- Company endpoints
│ └── PeopleController -- Contact endpoints
├── Guards
│ └── CrmSyncGuard -- Feature-flag guard
├── Decorators
│ └── @CrmSync() -- Metadata decorator
├── Types
│ ├── twenty-crm.types.ts -- CRM entity interfaces
│ └── mapping.types.ts -- Entity mapping interfaces
└── Utils
├── mapping.utils.ts -- Field transformation utilities
└── retry.utils.ts -- Retry with exponential backoff

Module Registration

TwentyCrmModule is a global dynamic module that supports two registration patterns:

Static Registration (forRoot)

TwentyCrmModule.forRoot({
twentyCrmConfig: {
apiUrl: 'https://crm.example.com',
apiKey: 'your-api-key',
workspaceId: 'workspace-id'
}
});

Async Registration (forRootAsync)

TwentyCrmModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
twentyCrmConfig: {
apiUrl: configService.get('TWENTY_CRM_BASE_URL'),
apiKey: configService.get('TWENTY_CRM_API_KEY')
}
}),
inject: [ConfigService]
});

Both methods register the module globally using @Global(), making its services available throughout the application without additional imports.

Configuration

CrmConfigService

The CrmConfigService reads all configuration from environment variables:

Environment VariableTypeDefaultDescription
TWENTY_CRM_BASE_URLstring--Twenty CRM API base URL
TWENTY_CRM_API_KEYstring--API key for authentication
TWENTY_CRM_WORKSPACE_IDstring--Workspace identifier
TWENTY_CRM_TIMEOUT_MSnumber30000Request timeout in milliseconds
TWENTY_CRM_MAX_RETRIESnumber3Maximum retry attempts
TWENTY_CRM_RETRY_DELAY_MSnumber1000Base delay between retries

Feature Detection

// Check if integration is properly configured
const isEnabled = crmConfigService.isEnabled;
// Returns true only if apiUrl, apiKey, AND workspaceId are all set

// Validate config throws if missing required vars
crmConfigService.validateConfig();

Services

TwentyCrmService (HTTP Client)

The core HTTP client for all Twenty CRM API interactions. It handles:

  • Bearer token authentication via Authorization header
  • Workspace scoping via X-Workspace-Id header
  • Automatic error mapping to NestJS HttpException
  • Structured logging for all requests
// Generic request method
async makeRequest<T>(
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
endpoint: string,
data?: any,
params?: any,
schema?: boolean, // Use metadata API endpoint
): Promise<T>

The service exposes two base URLs:

  • REST API: {apiUrl}/rest{endpoint} -- for data operations
  • Metadata API: {apiUrl}/rest/metadata{endpoint} -- for schema operations (when schema: true)

ClientService (CRUD Operations)

High-level service wrapping TwentyCrmService for typed entity operations:

MethodHTTPEndpointEntity
createCompany(data)POST/companiesTwentyOrganization
getCompany(id)GET/companies/:idTwentyOrganization
getCompanies()GET/companiesTwentyOrganization[]
updateCompany(id, data)PUT/companies/:idTwentyOrganization
deleteCompany(id)DELETE/companies/:idvoid
createContact(data)POST/contactsTwentyContact
getContact(id)GET/contacts/:idTwentyContact
getContacts()GET/contactsTwentyContact[]
updateContact(id, data)PUT/contacts/:idTwentyContact
deleteContact(id)DELETE/contacts/:idvoid
createDeal(data)POST/dealsTwentyDeal
getDeal(id)GET/deals/:idTwentyDeal
getDeals()GET/dealsTwentyDeal[]
updateDeal(id, data)PUT/deals/:idTwentyDeal
deleteDeal(id)DELETE/deals/:idvoid
createProduct(data)POST/productsTwentyProduct
getProduct(id)GET/products/:idTwentyProduct
getProducts()GET/productsTwentyProduct[]
updateProduct(id, data)PUT/products/:idTwentyProduct
deleteProduct(id)DELETE/products/:idvoid

CrmTenantService (Multi-Tenancy)

Manages tenant context resolution for multi-tenant CRM operations:

// Resolve tenant from work or global context
const context = crmTenantService.resolveTenantContext(
workId, // Optional: creates "work_{id}" tenant
userId, // Optional: for audit context
globalTenantId // Optional: fallback to "global_everworks"
);

// Get tenant-specific API prefix
const prefix = crmTenantService.getTenantEndpointPrefix(context);
// Returns: "/tenants/work_abc123"

Controllers

CompaniesController

Route: api/twenty-crm/companies (JWT-protected)

MethodRouteDescription
GET /List all companiesReturns all organizations
POST /Create companyCreates a new organization
PATCH /:idUpdate companyUpdates organization by ID
DELETE /:idDelete companyRemoves organization by ID

PeopleController

Handles contact/person CRUD. When creating a contact, only these fields are forwarded:

{
(firstName, lastName, email, phone, companyId, position, avatarUrl);
}

CRM Entity Types

TwentyOrganization

interface TwentyOrganization {
id?: string;
name: string;
domainName?: string;
address?: string;
employees?: number;
linkedinUrl?: string;
xUrl?: string;
annualRecurringRevenue?: number;
idealCustomerProfile?: boolean;
}

TwentyContact

interface TwentyContact {
id?: string;
firstName?: string;
lastName?: string;
email?: string;
phone?: string;
companyId?: string;
position?: string;
avatarUrl?: string;
}

TwentyDeal

interface TwentyDeal {
id?: string;
title: string;
amount?: number;
currency?: string;
stage?: string;
probability?: number;
companyId?: string;
personId?: string;
}

TwentyProduct

interface TwentyProduct {
id?: string;
name: string;
description?: string;
price?: number;
currency?: string;
category?: string;
}

Guards and Decorators

CrmSyncGuard

A CanActivate guard that blocks requests when the CRM integration is disabled or misconfigured:

@UseGuards(CrmSyncGuard)
@Post('sync')
async syncData() { ... }

The guard checks CrmConfigService.isEnabled and runs validateConfig(). If either check fails, the request is blocked with a false return (HTTP 403).

@CrmSync Decorator

A metadata decorator for marking routes that require CRM sync:

@CrmSync() // enabled = true (default)
@CrmSync(false) // disabled

Sets the crm_sync metadata key, which can be read by guards or interceptors.

Retry Utilities

The RetryUtils class provides resilient API communication:

// Retry with exponential backoff
const result = await RetryUtils.withRetry(
() => apiCall(),
3, // maxAttempts
1000, // delayMs
2 // backoffMultiplier
);

// Check if an error is retryable
RetryUtils.isRetryableError(error);
// true for: ECONNRESET, ETIMEDOUT, ENOTFOUND, 5xx, 429

// Calculate delay with jitter (prevents thundering herd)
const delay = RetryUtils.calculateRetryDelay(1000, attempt, 2, 30000);

Entity Mapping

The mapping system (mapping.types.ts) defines how Ever Works entities map to Twenty CRM entities:

Ever Works EntityTwenty CRM EntityKey Mapping
EverWorksCompanyTwentyOrganizationwebsite -> domainName, size -> employees
EverWorksClientTwentyContactDirect field mapping
EverWorksItemTwentyProductDirect field mapping

Field mappings support custom transforms, required field validation, and detailed transformation logging.

Source Files

FilePurpose
apps/api/src/integrations/index.tsModule barrel export
apps/api/src/integrations/twenty-crm/twenty-crm.module.tsDynamic module definition
apps/api/src/integrations/twenty-crm/config/crm-config.service.tsConfiguration service
apps/api/src/integrations/twenty-crm/services/twenty-crm.service.tsHTTP client
apps/api/src/integrations/twenty-crm/services/client.service.tsCRUD operations
apps/api/src/integrations/twenty-crm/services/crm-tenant.service.tsMulti-tenant context
apps/api/src/integrations/twenty-crm/controllers/companies.service.tsCompanies controller
apps/api/src/integrations/twenty-crm/controllers/people.controler.tsPeople controller
apps/api/src/integrations/twenty-crm/guards/crm-sync.guard.tsFeature gate guard
apps/api/src/integrations/twenty-crm/decorators/crm-sync.decorator.tsMetadata decorator
apps/api/src/integrations/twenty-crm/types/twenty-crm.types.tsCRM type definitions
apps/api/src/integrations/twenty-crm/types/mapping.types.tsMapping type definitions
apps/api/src/integrations/twenty-crm/utils/retry.utils.tsRetry utilities