Dot Domino CRM — Developer Architecture

System design · Service boundaries · Data flow · API contracts · Queue topology · DB schema outline · Deployment topology

Backend Frontend Integrations Queues DB DevOps
1 · System Layer Overview
L1
Client Layer
Browser SPA + Mobile (React Native / PWA). All communication over HTTPS REST + WebSocket for real-time notifications.
React 18 + Vite React Query Zustand WebSocket (Socket.io client) PWA / React Native
L2
API Gateway / BFF Layer
Single entry point. Handles auth (JWT + refresh tokens), rate limiting, request routing, API versioning, and WebSocket upgrade. Acts as Backend-for-Frontend.
Kong / AWS API Gateway JWT Auth middleware Rate limiter (Redis) SSL termination Socket.io server
L3
Microservices / Domain Services
Each domain is an independent service with its own DB schema. Services communicate via internal REST or message queue. Stateless, horizontally scalable.
lead-service contact-service deal-service campaign-service communication-service integration-service billing-service report-service notification-service agent-service
L4
Async Layer — Message Queue + Workers
All heavy/async work (drip sends, fallback chain, webhook ingest, report generation) goes through a queue. Workers pick tasks, process, and update state. Retry logic built in.
BullMQ (Redis-backed) drip-worker fallback-worker ingest-worker report-worker notification-worker
L5
Data Layer
Primary relational store + time-series for events + cache + object storage for files. Each service owns its schema (logical separation, shared cluster in early phase).
PostgreSQL 15 (primary) TimescaleDB (events/analytics) Redis 7 (cache + queues) S3 / R2 (files, PDFs) Elasticsearch (search)
L6
External Integrations Layer
All third-party connections are isolated behind adapter classes. Each adapter normalises the external API into a common LeadPayload schema before handing off to ingest-worker.
IndiaMART adapter TradeIndia adapter Meta Lead Ads adapter Google Ads adapter LinkedIn adapter WhatsApp BSP adapter SMS gateway adapter RCS adapter Razorpay/PayU adapter ERP/Tally adapter
2 · End-to-End Lead Ingestion Flow
External Sources
IndiaMART
POST /webhook/indiaMart
Meta Lead Ads
FB Webhook verify
Website Form
POST /api/v1/leads/form
Social Comment
Social listener webhook
API Gateway (L2)
Signature verify
HMAC SHA256
Auth / API key
JWT or static key
Rate limit
100 req/min/source
Route to
integration-service
integration-service → Adapter → Normalise
Source Adapter
parse raw payload
Normalise to
LeadPayload schema
Enqueue
Queue: lead.ingest
HTTP 200 OK
ACK to source
↓ async
ingest-worker (BullMQ consumer)
Deduplicate
phone + email hash
Enrich
Apollo / internal
Score
rules engine
Assign
round-robin / rules
Write to DB
leads table
↓ emits domain event
campaign-service
Enrol in drip sequence
notification-service
Alert assigned agent
report-service
Increment counters
WebSocket push
Live dashboard update
3 · Multi-Channel Fallback Chain — Technical Flow
WhatsApp
Primary · BSP API
status: delivered?
undelivered / 30 min
SMS
Fallback 1 · SMSC
status: delivered?
DND / invalid number
RCS
Fallback 2 · RBM API
device supported?
not supported
Email
Fallback 3 · SMTP
hard bounce?
bounce
Agent Task
Final escalation
manual outreach
// fallback-worker.ts — BullMQ job processor async function processFallbackJob(job: Job<FallbackPayload>) { const { leadId, stepId, attemptChain } = job.data; for (const channel of attemptChain) { const result = await sendViaChannel(channel, leadId, stepId); await db.communicationLog.insert({ leadId, channel, status: result.status, sentAt: new Date(), messageId: result.messageId }); if (result.status === 'delivered') break; // stop chain on success // wait configured window before next fallback await sleep(FALLBACK_DELAY_MS[channel]); // {WA: 30min, SMS: 15min, RCS: 10min} } if (allFailed) { await taskService.createAgentTask({ leadId, reason: 'all_channels_failed' }); } }

Delivery Status Polling

  • WhatsApp: BSP webhook message.status callback
  • SMS: DLR (Delivery Report) webhook from SMSC
  • RCS: RBM API delivery webhook
  • Email: SMTP bounce webhook (SendGrid event)
  • All statuses persisted to communication_log table

Retry & Dead Letter Queue

  • BullMQ job retries: 3 attempts with exponential backoff
  • Failed jobs → DLQ (Dead Letter Queue) for inspection
  • DLQ alert fires to ops channel (Slack/PagerDuty)
  • Manual replay available from admin dashboard
  • All fallback stats feed Report module 7
4 · Drip Campaign Engine — Internal Architecture
Campaign Service — Sequence Execution
Trigger event received
lead.created / stage.changed / etc.
Find matching sequences
WHERE trigger_type = event AND active
Create enrollment
sequence_enrollments table
Schedule Step 1
BullMQ delayed job (delay = 0)
drip-worker executes
send via channel / fallback chain
Evaluate condition
opened? clicked? replied?
↓ condition result
Branch: YES path
next step in sequence
|
Branch: NO path
alternate step / wait longer
Schedule next step
BullMQ delayed by delay_ms
↓ after all steps
Mark enrollment complete
status = completed / converted
Emit drip.completed event
report-service increments
// DB schema — key tables for drip engine CREATE TABLE sequences ( id UUID PRIMARY KEY, name TEXT, trigger_type TEXT, -- 'lead_created' | 'stage_changed' | 'no_reply' | ... trigger_config JSONB, -- {stage: 'qualified', delay_days: 2} is_active BOOLEAN, org_id UUID ); CREATE TABLE sequence_steps ( id UUID PRIMARY KEY, sequence_id UUID REFERENCES sequences, step_order INT, step_type TEXT, -- 'send_message' | 'wait' | 'condition' | 'update_stage' channel_priority TEXT[], -- ['whatsapp','sms','rcs','email'] ← fallback chain delay_ms BIGINT, -- delay before this step fires template_id UUID, condition JSONB, -- {event: 'opened', window_ms: 86400000} yes_step_id UUID, -- branch if condition true no_step_id UUID -- branch if condition false ); CREATE TABLE sequence_enrollments ( id UUID PRIMARY KEY, lead_id UUID, sequence_id UUID, current_step UUID, status TEXT, -- 'active' | 'paused' | 'completed' | 'converted' | 'unsubscribed' enrolled_at TIMESTAMPTZ, completed_at TIMESTAMPTZ ); CREATE TABLE communication_log ( id UUID PRIMARY KEY, lead_id UUID, step_id UUID, channel TEXT, -- which channel was actually used status TEXT, -- 'sent' | 'delivered' | 'opened' | 'clicked' | 'bounced' | 'failed' external_id TEXT, -- BSP message ID sent_at TIMESTAMPTZ, delivered_at TIMESTAMPTZ );
5 · Core API Contracts

LEAD ENDPOINTS

MethodPathDescription
POST/api/v1/leadsCreate lead (internal)
POST/api/v1/leads/ingestWebhook ingest (public, signed)
GET/api/v1/leadsList leads (paginated, filtered)
GET/api/v1/leads/:idLead detail + timeline
PATCH/api/v1/leads/:idUpdate status / owner / fields
POST/api/v1/leads/:id/convertConvert lead to deal
POST/api/v1/leads/bulk-importCSV / B2B DB import

CAMPAIGN ENDPOINTS

MethodPathDescription
POST/api/v1/sequencesCreate drip sequence
POST/api/v1/sequences/:id/enrolManually enrol lead
PATCH/api/v1/sequences/:id/stepsUpdate step order / config
GET/api/v1/sequences/:id/statsOpen/click/convert stats
POST/api/v1/webhooks/deliveryBSP delivery status callback
POST/api/v1/webhooks/email-eventSendGrid event webhook

DEAL / BILLING ENDPOINTS

MethodPathDescription
POST/api/v1/dealsCreate deal
PATCH/api/v1/deals/:id/stageMove pipeline stage
POST/api/v1/deals/:id/quotationGenerate quotation PDF
POST/api/v1/deals/:id/invoiceGenerate GST invoice
POST/api/v1/deals/:id/payment-linkCreate Razorpay/PayU link
POST/api/v1/webhooks/paymentPayment gateway callback

INTEGRATION / REPORT ENDPOINTS

MethodPathDescription
POST/api/v1/integrations/sourcesRegister new source
GET/api/v1/integrations/sources/:id/testTest connection
GET/api/v1/reports/lead-intakeReport module 1
GET/api/v1/reports/agent-productivityReport module 10
GET/api/v1/reports/drip-performanceReport module 4
GET/api/v1/reports/customCustom report builder
6 · Database Schema — Core Tables
-- ORGANISATIONS (multi-tenant) organisations (id, name, plan, settings, created_at) users (id, org_id, email, role, password_hash, last_active) -- LEAD DOMAIN leads (id, org_id, first_name, last_name, email, phone, company, source, source_meta JSONB, score, status, owner_id, segment_tags TEXT[], is_duplicate, created_at, updated_at) lead_timeline (id, lead_id, event_type, payload JSONB, actor_id, created_at) lead_scores (id, lead_id, score_version, breakdown JSONB, scored_at) -- CONTACT / DEAL DOMAIN contacts (id, org_id, first_name, last_name, email, phone, company_id) companies (id, org_id, name, industry, size, website) deals (id, org_id, lead_id, contact_id, title, value, currency, stage, probability, expected_close, owner_id, created_at, won_at, lost_at, lost_reason) deal_products (id, deal_id, product_id, qty, unit_price, discount) -- COMMERCIAL products (id, org_id, name, sku, unit_price, currency, updated_at) quotations (id, deal_id, version, pdf_url, shared_at, viewed_at, status) invoices (id, deal_id, quotation_id, invoice_no, total, tax, status, paid_at, payment_ref) payment_links (id, invoice_id, gateway, external_id, url, status, created_at) -- CAMPAIGN / DRIP DOMAIN sequences (as above) sequence_steps (as above) sequence_enrollments (as above) message_templates (id, org_id, channel, name, body, variables TEXT[], created_at) ab_test_variants (id, step_id, variant_key, template_id, weight INT) communication_log (as above) -- INTEGRATION integration_sources (id, org_id, source_type, config JSONB, webhook_secret, active) chat_sync_log (id, source_id, lead_id, external_thread_id, direction, body, sent_at) -- REPORTING (TimescaleDB hypertables) metric_events (time TIMESTAMPTZ, org_id, metric_key, value FLOAT, dims JSONB)
7 · Queue Topology (BullMQ)
Queue NameProducerConsumer (Worker)Job TypesConcurrency
lead.ingestintegration-serviceingest-workerdeduplicate, enrich, score, assign, write20
drip.stepcampaign-servicedrip-workerexecute step, evaluate condition, schedule next50
communication.senddrip-workerfallback-workerWA send → SMS → RCS → Email → agent task30
delivery.statusBSP webhooksdelivery-workerupdate comm_log, trigger next step if needed40
notification.pushany servicenotification-workeremail alert, SMS alert, in-app push, WebSocket25
report.computescheduler (cron)report-workeraggregate metrics, update dashboards5
chat.syncintegration-servicechat-sync-workerpull IndiaMART/TradeIndia messages, upsert timeline10
social.triggersocial listenersocial-workercomment→DM trigger, enrol in sequence15
8 · Integration Adapter Pattern
// All source adapters implement this interface interface LeadSourceAdapter { verifySignature(headers: Headers, body: Buffer): boolean; normalise(rawPayload: unknown): LeadPayload; acknowledgeReceived(): HttpResponse; // must return 200 fast } // Normalised schema — every source adapter outputs this interface LeadPayload { source: 'indiaMart' | 'tradeIndia' | 'metaAds' | 'googleAds' | 'linkedIn' | 'website' | 'events' | 'outbound' | 'b2bDb'; externalId: string; // source's own lead ID firstName: string; lastName?: string; email?: string; phone?: string; company?: string; message?: string; // buyer's inquiry text sourceMeta: Record<string, unknown>; // raw source-specific fields receivedAt: Date; } // Example: IndiaMART adapter class IndiaMartAdapter implements LeadSourceAdapter { normalise(raw: IndiaMartPayload): LeadPayload { return { source: 'indiaMart', externalId: raw.UNIQUE_QUERY_ID, firstName: raw.SENDER_NAME.split(' ')[0], phone: raw.SENDER_MOBILE, email: raw.SENDER_EMAIL, company: raw.SENDER_COMPANY, message: raw.QUERY_MESSAGE, sourceMeta: raw, receivedAt: new Date(raw.QUERY_TIME) }; } }
9 · Real-Time Architecture (WebSocket)

Connection

  • Client connects on auth with JWT
  • Joins room org:{orgId} + user:{userId}
  • Socket.io on Node.js gateway
  • Redis adapter for multi-node pub/sub

Events pushed to client

  • lead.new — new lead arrives
  • lead.assigned — assigned to me
  • deal.stage_changed
  • message.delivered
  • payment.received
  • task.created

Dashboard live counters

  • Report service publishes metric deltas
  • Gateway broadcasts to org room
  • Frontend patches local state via React Query
  • No full page refresh needed
  • Reconnect with exponential backoff
10 · Deployment Topology
Traffic ingress
Cloudflare (DDoS + CDN)
Load Balancer (ALB / Nginx)
API Gateway / BFF
N pods, horizontally scaled
Services (Kubernetes / Docker Compose)
lead-service
deal-service
campaign-service
integration-service
billing-service
report-service
Workers
ingest-worker
drip-worker
fallback-worker
delivery-worker
chat-sync-worker
report-worker
Data stores
PostgreSQL 15
Primary + read replica
TimescaleDB
Events + metrics
Redis 7
Cache + BullMQ
S3 / R2
PDFs, files
Elasticsearch
Full-text search
Observability
Prometheus + Grafana
OpenTelemetry traces
Sentry (error tracking)
Loki (log aggregation)
PagerDuty (alerts)
11 · Security & Auth Model

Authentication

  • JWT access token (15 min TTL)
  • Refresh token (30 days, httpOnly cookie)
  • OAuth 2.0 for Meta/Google/LinkedIn
  • TOTP 2FA optional per org
  • API key auth for webhooks (static, signed)

Authorisation (RBAC)

  • Roles: Admin · Manager · Agent · View-only
  • Row-level: agent sees only own leads
  • Org-level: strict multi-tenant isolation
  • All DB queries filter by org_id
  • Permission checked in middleware layer

Data & Transport

  • TLS 1.3 everywhere
  • PII fields encrypted at rest (AES-256)
  • Webhook payloads HMAC-verified
  • Secrets in Vault / AWS Secrets Manager
  • GDPR: right-to-erase on lead/contact
12 · Environment Config Reference
# Core DATABASE_URL=postgres://user:pass@pg-host:5432/dotdomino REDIS_URL=redis://redis-host:6379 JWT_SECRET=...32-byte-random... JWT_REFRESH_SECRET=...32-byte-random... # Storage S3_BUCKET=dotdomino-files S3_REGION=ap-south-1 # Integrations INDIAMARTGLOBAL_KEY=... TRADEINDIA_WEBHOOK_SECRET=... META_APP_ID=... META_APP_SECRET=... GOOGLE_ADS_DEVELOPER_TOKEN=... LINKEDIN_CLIENT_ID=... # Communication WA_BSP_URL=https://api.gupshup.io/sm/api/v1 WA_BSP_KEY=... SMS_GATEWAY_URL=... SMS_GATEWAY_KEY=... RCS_API_KEY=... SENDGRID_API_KEY=... # Payments RAZORPAY_KEY_ID=... RAZORPAY_KEY_SECRET=... # Observability SENTRY_DSN=... OTEL_EXPORTER_ENDPOINT=http://otel-collector:4317

Dot Domino CRM — Developer Architecture Document · Generated from product blueprint