Files

4.0 KiB

TwentyCRM — DRE CRM Deployment Notes

Architecture (Docker Compose on Core)

4 containers: twenty-server (NestJS API + React frontend), twenty-worker (BullMQ jobs), twenty-db (PostgreSQL 16), twenty-redis (cache/queue).

PITFALL: Frontend API calls go to localhost

TwentyCRM's frontend SPA is served by the same NestJS server. At startup, the container runs inject-runtime-env.sh which reads REACT_APP_SERVER_BASE_URL from the environment and injects it into the HTML. If this env var is missing, the frontend defaults to http://localhost:3000 for all API calls.

Fix: Both SERVER_URL AND REACT_APP_SERVER_BASE_URL must be set in docker-compose.yml:

environment:
  NODE_PORT: 3000
  SERVER_URL: ${SERVER_URL}
  REACT_APP_SERVER_BASE_URL: ${SERVER_URL}    # ← Critical

Test: After restart, verify:

curl -s https://crm.debtrecoveryexperts.com/ | grep 'REACT_APP_SERVER_BASE_URL'
# Should show the real domain, not localhost

PITFALL: .env changes need full restart

After changing .env, run docker compose down && docker compose up -d. Just restarting the server container is NOT enough — the frontend build is baked into the image and runs the inject script at container startup.

PITFALL: DNS propagation for new subdomains

When creating a new subdomain (e.g., crm.debtrecoveryexperts.com), the initial A record (non-proxied) can take 5-15 minutes to propagate. If the user gets ERR_NAME_NOT_RESOLVED from their browser, switch to Cloudflare proxied (orange cloud) mode for instant resolution.

PITFALL: Port conflict with DocuSeal (port 3000)

DocuSeal already uses port 3000. Map TwentyCRM to 3001:

ports:
  - "3001:3000"

API Field Creation

REST endpoint for field creation (TEXT, NUMBER, CURRENCY, DATE, etc.):

POST https://crm.debtrecoveryexperts.com/metadata
Body: {"query":"mutation{createOneField(input:{field:{name:\"fieldName\",label:\"Field Label\",type:TEXT,objectMetadataId:\"...\"}}){id name}}"}

RESERVED field names: Avoid address as a field name — TwentyCRM rejects it with validation errors. Use physicalAddress or mailingAddress instead.

RELATION fields: Creating relationships (foreign keys) via the REST API requires a relationCreationPayload parameter whose exact shape is not well documented. The UI is the reliable path for creating relationships.

Object IDs for DRE custom objects (July 7, 2026):

  • Claims: 0c6808cf-b5e8-4f39-8c4f-6dc6b7d029f3
  • Debtors: bac887ba-22a9-46fe-b988-e1985ee19b29
  • Payments: bde74e7b-b1c2-48eb-be5b-f3ea2207873c
  • Case Notes: 176736a7-6d3b-4129-ac03-67cea102d618

DRE Custom Data Model

Claims Fields

Field Type
claimNumber TEXT
claimAmount NUMBER
debtType TEXT
currentTier TEXT
status TEXT
state TEXT
dateFiled DATE
aiScore NUMBER
description TEXT

Debtors Fields

Field Type
businessName TEXT
contactName TEXT
email TEXT
phone TEXT
address TEXT
businessType TEXT
notes TEXT

Payments Fields

Field Type
paymentAmount NUMBER
paymentDate DATE
paymentMethod TEXT
dreFee NUMBER
costsDeducted NUMBER
netToClient NUMBER

Case Notes Fields

Field Type
content TEXT
timestamp DATE

Default Workflows to Delete

Twenty ships with sample workflows: "Create company when adding a new person" and "Quick Lead". Delete via REST:

for wid in "id1" "id2"; do
  curl -X DELETE "https://crm.debtrecoveryexperts.com/rest/workflows/$wid" -H "Authorization: Bearer $TOKEN"
done

Check remaining: curl -s https://crm.debtrecoveryexperts.com/rest/workflows -H "Authorization: Bearer $TOKEN" | python3 -c "import sys,json; [print(w['name']) for w in json.load(sys.stdin)['data']['workflows']]"

Relationships still need to be set up via the UI: Claims → Debtor, Payments → Claim, Case Notes → Claim.