How juanfer93/passive_crm_nestjs works
1. This repo is a NestJS backend that sits between inbound messaging channels and the VIVA OS. Its core job is conversational intake: receive lead messages, maintain conversational memory, qualify the lead, extract structured fields, and publish events outward. It does not own operational execution such as calls, tasks, appointments, or follow-up execution; that boundary belongs to VIVA.
2. The application is split into three feature modules under src/features: meta-messaging-webhook, sofia-engine, and whatsapp-web. The first is the main conversational pipeline, the second is the internal event/decision bridge to VIVA, and the third is a transport adapter for WhatsApp Web that feeds the same conversation engine.
3. In meta-messaging-webhook, the architecture follows Nest’s controller → use case → domain service → infrastructure adapter layering. The presentation layer exposes the webhook controller and signature guard; the application layer contains the orchestration use cases such as accepting the webhook, verifying it, processing incoming messages, ensuring user profiles, and running due follow-ups; the domain layer holds entities, ports, and pure services; the infrastructure layer implements adapters for Mongo, Meta, OpenAI, GHL, VIVA, and background execution.
4. The inbound message flow starts at a Meta webhook request, passes through MetaSignatureGuard, then the controller hands it to accept-meta-webhook / verify-meta-webhook and the message-processing use case. Before any conversational logic runs, the system checks idempotency using a Mongo-backed message receipt store so duplicate platform deliveries do not create duplicate side effects.
5. Conversation state is persisted in MongoDB and treated as the source of truth for conversational memory. The repository includes schemas for conversation state and message receipts, plus domain entities for customer profile, dealer profile, assistant context, follow-up state, incoming messages, media content, and lead custom fields. This is the main data boundary where raw channel events become durable lead memory.
6. The AI/conversation subsystem is centered on extracting structured lead data from unstructured chat. OpenAI adapters are used for assistant replies, media analysis, and likely transcript/image understanding, while domain services normalize and derive fields such as vehicle interest, down payment, language, appointment date, and lead temperature. Dealer-specific prompts shape the conversational behavior and qualification flow.
7. The system also has a clear media-handling branch: audio is transcribed, images are analyzed, and unsupported media is marked as non-textual while the conversation continues. That keeps the qualification flow resilient across Messenger, Instagram, and WhatsApp without making media processing a hard dependency for every incoming message.
8. Qualification and profile enrichment are separated from transport concerns. Services such as dealer profile resolution, lead custom field normalization, courtesy reply generation, and reactivation/follow-up logic encapsulate business rules for turning message history into actionable lead state. The application layer coordinates these rules rather than embedding them in controllers.
9. External CRM and business-event integrations are adapters behind ports. The repo includes sinks for GHL and a composite CRM sink, plus a Viva Sofia event publisher. This means the conversation engine can sync lead updates outward without knowing whether the target is GHL, VIVA, or both.
10. The VIVA boundary is explicit and important: NestJS emits Sofia events like lead.created, lead.updated, buyer_dna_updated, appointment.created, and document.received to POST /api/sofia/event. The sofia-engine module is the inbound side of that contract on the VIVA-facing boundary, with its own controller, access guard, application services, and Mongo persistence for Sofia actions, activities, and recommendations.
11. Background work is isolated from request handling. The background infrastructure contains a Node task runner and a follow-up worker, and there is a process-due-follow-ups use case in the application layer. This separation lets the webhook path stay low-latency while scheduled or deferred actions run asynchronously.
12. WhatsApp is treated as an additional transport, not a separate brain. The whatsapp-web module provides a client service and controller so WhatsApp Web messages enter the same conversational architecture, reusing the same memory, extraction, and event-emission patterns as Meta messaging.
13. The repository is organized as a classic Nest monolith with strong internal boundaries rather than microservices. AppModule composes the feature modules, while the surrounding scripts support simulation of chat and media flows for local testing. The architecture diagram should therefore show one NestJS runtime with multiple channel ingress points, a shared conversation core, Mongo persistence, and outbound adapters to OpenAI, CRM systems, and VIVA.
Main entry points: process-incoming-meta-message.use-case.ts Process message · app.module.ts App module · meta-messaging-webhook.controller.ts Webhook controller · sofia-engine.controller.ts Sofia controller
Components 29
Nest runtime
Application bootstrap and composed feature modules
- App bootstrap · Starts the Nest application and composes feature modules
src/main.ts - App module · Root module wiring the repository features together
src/app.module.ts
Meta messaging
Main conversational intake pipeline
- Meta module · Webhook intake and conversational processing feature
src/features/meta-messaging-webhook/meta-messaging-webhook.module.ts - Webhook controller · Receives Meta webhook traffic and delegates orchestration
src/features/meta-messaging-webhook/presentation/controllers/meta-messaging-webhook.controller.ts - Signature guard · Verifies Meta webhook signatures before processing
src/features/meta-messaging-webhook/presentation/guards/meta-signature.guard.ts - Accept webhook · Entry orchestration for inbound webhook delivery
src/features/meta-messaging-webhook/application/use-cases/accept-meta-webhook.use-case.ts - Verify webhook · Handles webhook verification handshake
src/features/meta-messaging-webhook/application/use-cases/verify-meta-webhook.use-case.ts - Process message · Runs the core message intake and conversation update flow
src/features/meta-messaging-webhook/application/use-cases/process-incoming-meta-message.use-case.ts - Ensure profile · Resolves or creates the user profile before qualification
src/features/meta-messaging-webhook/application/use-cases/ensure-meta-user-profile.use-case.ts - Due follow-ups · Processes deferred follow-up work
src/features/meta-messaging-webhook/application/use-cases/process-due-follow-ups.use-case.ts - Message extractor · Normalizes webhook payloads into internal message data
src/features/meta-messaging-webhook/application/services/meta-webhook-message-extractor.service.ts - Dealer profile · Resolves dealer-specific qualification behavior and prompts
src/features/meta-messaging-webhook/application/services/dealer-profile-resolver.service.ts - Field normalizer · Derives structured lead fields from conversation state
src/features/meta-messaging-webhook/domain/services/lead-custom-fields-normalizer.service.ts - Conversation store · Persists conversation state as the source of truth
src/features/meta-messaging-webhook/infrastructure/mongo/mongo-conversation-state.repository.ts - Receipt store · Deduplicates repeated platform deliveries
src/features/meta-messaging-webhook/infrastructure/mongo/mongo-message-idempotency.store.ts - OpenAI adapter · Generates assistant replies and analyzes media or transcripts
src/features/meta-messaging-webhook/infrastructure/openai/openai-assistant.adapter.ts - CRM sink · Composite outbound sync toward CRM systems
src/features/meta-messaging-webhook/infrastructure/hln/composite-crm-sink.adapter.ts - VIVA publisher · Emits Sofia events outward to VIVA
src/features/meta-messaging-webhook/infrastructure/viva/viva-sofia-event.adapter.ts - Background runner · Runs deferred work outside request handling
src/features/meta-messaging-webhook/infrastructure/background/node-background-task-runner.ts - Follow-up worker · Executes scheduled follow-up actions
src/features/meta-messaging-webhook/infrastructure/background/node-follow-up-worker.ts
Sofia engine
VIVA-facing event and decision bridge
- Sofia module · Inbound VIVA-side engine for Sofia actions and recommendations
src/features/sofia-engine/sofia-engine.module.ts - Sofia controller · Receives Sofia events on the VIVA-facing boundary
src/features/sofia-engine/presentation/controllers/sofia-engine.controller.ts - Access guard · Protects the Sofia event ingress
src/features/sofia-engine/presentation/guards/sofia-access.guard.ts - Webhook bridge · Bridges inbound events into Sofia processing
src/features/sofia-engine/application/services/sofia-webhook-bridge.service.ts - Sofia logic · Decision, context, learning, action, and activity services
src/features/sofia-engine/application/services/sofia-decision.service.ts - Sofia storage · Stores Sofia actions, activities, and recommendations
src/features/sofia-engine/infrastructure/mongo/schemas/sofia-action.schema.ts
WhatsApp web
Additional inbound transport
- WhatsApp module · WhatsApp Web transport adapter
src/features/whatsapp-web/whatsapp-web.module.ts - WhatsApp controller · Ingress for WhatsApp Web messages
src/features/whatsapp-web/presentation/controllers/whatsapp-web.controller.ts - Client service · Handles WhatsApp Web client interactions
src/features/whatsapp-web/application/services/whatsapp-web-client.service.ts
Connections 27
- App bootstrap → App module: bootstraps · Application entrypoint initializes the root module
- App module → Meta module: imports · Composes the conversational intake feature
- App module → Sofia module: imports · Composes the VIVA-facing Sofia bridge
- App module → WhatsApp module: imports · Composes the WhatsApp transport feature
- Webhook controller → Signature guard: protected by · Webhook ingress is signed before handling
- Webhook controller → Accept webhook: delegates · Primary webhook intake passes to orchestration
- Webhook controller → Verify webhook: delegates · Verification requests use dedicated handshake logic
- Accept webhook → Process message: invokes · Accepted deliveries proceed into message processing
- Process message → Receipt store: checks · Duplicate platform deliveries are filtered first
- Process message → Message extractor: parses · Webhook payloads are normalized into internal message data
- Process message → Ensure profile: ensures · User profile is ready before qualification logic runs
- Process message → Dealer profile: loads · Dealer-specific behavior shapes the conversation flow
- Process message → Conversation store: reads/writes · Conversation memory is persisted as the source of truth
- Process message → OpenAI adapter: uses · Assistant replies and media understanding rely on AI adapters
- Process message → Field normalizer: derives · Structured lead fields are extracted from conversation state
- Process message → CRM sink: syncs · Lead updates are pushed to external CRM sinks
- Process message → VIVA publisher: emits · Lead and qualification events are published to VIVA
- Process message → Background runner: schedules · Deferred work is offloaded from the request path
- Due follow-ups → Follow-up worker: runs on · Follow-up processing executes asynchronously
- Background runner → Follow-up worker: dispatches · Background runner hands off deferred tasks
- Sofia controller → Access guard: protected by · Sofia ingress is access-controlled
- Sofia controller → Webhook bridge: delegates · Inbound Sofia events enter through the bridge service
- Webhook bridge → Sofia logic: coordinates · Bridge forwards events into Sofia processing services
- Sofia logic → Sofia storage: persists · Decisions, actions, and recommendations are stored in Mongo
- VIVA publisher → Sofia controller: delivers · Meta-side events are consumed by the Sofia boundary
- WhatsApp controller → Client service: uses · Controller delegates transport handling to the client service
- WhatsApp controller → Process message: feeds · WhatsApp messages enter the same conversation engine