Saltar al contenido principal

Communications Module

Multi-channel notification system for FlowPOS supporting Email (SendGrid), SMS (Twilio), WhatsApp (Twilio), and PDF generation (Puppeteer).

Architecture

The module follows Hexagonal Architecture (ports & adapters):

communications/
├── domain/ # Pure domain types & contracts
│ ├── communication.types.ts # SendCommunicationPayload, ICommunicationChannel, etc.
│ ├── communications-repository.domain.ts # ICommunicationsRepository port
│ └── html-utils.ts # Shared HTML-to-text utility
├── application/ # Business logic & orchestration
│ ├── communications.service.ts # Main orchestration service
│ ├── adapters/ # Channel adapter implementations
│ │ ├── email-adapter.service.ts
│ │ ├── sms-adapter.service.ts
│ │ └── whatsapp-adapter.service.ts
│ ├── services/ # Supporting services
│ │ ├── channel-factory.service.ts
│ │ ├── template-renderer.service.ts
│ │ ├── attachment-handler.service.ts
│ │ ├── recipient-resolver.service.ts
│ │ ├── invite-notification.service.ts
│ │ ├── rate-limiter.service.ts
│ │ ├── event-tracking.service.ts
│ │ ├── communication-helpers.service.ts
│ │ ├── communication-pdf.service.ts
│ │ └── public-document-link.service.ts
│ ├── events/ # Event handlers (event-driven)
│ │ ├── on-create-invite.handler.ts
│ │ ├── on-resend-invite.handler.ts
│ │ ├── on-create-low-stock-alert.handler.ts
│ │ ├── on-queue-job-processed.handler.ts
│ │ └── on-communication-status-changed.handler.ts
│ └── use-cases/
│ └── generate-communication-pdf.use-case.ts
├── infrastructure/ # External service integrations
│ ├── communications.repository.ts # Kysely database implementation
│ └── providers/
│ └── twilio.provider.ts # Twilio client management
├── interfaces/ # HTTP layer
│ ├── communications.controller.ts # CRUD + send + process endpoints
│ ├── webhooks.controller.ts # Provider webhook handlers
│ ├── dtos/ # Request validation
│ └── query/ # Pagination/filter queries
└── templates/ # HTML email templates

Dependency Rules

  • Domain has zero framework dependencies
  • Application depends only on domain ports (interfaces)
  • Infrastructure implements domain ports
  • Interfaces map HTTP requests to application calls

The service depends on ICommunicationsRepository (injected via token "ICommunicationsRepository"), not the concrete CommunicationsRepository.

Key Flows

Send Communication

POST /communications/send
→ CommunicationsService.send()
→ Validate channel provider configured
→ Check recipient preferences (opt-out)
→ Render template if templateId provided
→ Validate attachments
→ Create communication record (status=PENDING)
→ Save attachments to DB
→ Enqueue to BullMQ (CommunicationQueueModule)
→ Return communication record

Queue Processing

Queue processor picks job
→ CommunicationsService.executeSend()
→ Retrieve attachments from DB
→ ChannelFactory.getAdapter(channel)
→ adapter.send(payload) → SendGrid/Twilio API
→ Update communication record (status, messageId, provider response)

Webhook Delivery Tracking

POST /webhooks/sendgrid/events      (public, no auth)
POST /webhooks/twilio/sms-status (public, no auth)
POST /webhooks/twilio/whatsapp-status

→ Map provider status → CommunicationStatus
→ Find communication by providerMessageId
→ Update status + timestamp
→ Trigger OnCommunicationStatusChangedHandler

Recipient Resolution (Low Stock Alerts)

OnCreateLowStockAlertEvent triggered
→ RecipientResolverService.resolveRecipients()
→ Query active recipient rules (role / group / ad_hoc)
→ Resolve each rule to actual contacts
→ Deduplicate
→ Send notification to each resolved recipient

API Endpoints

Communications Controller (/communications)

MethodPathDescription
POST/communicationsCreate a communication record
POST/communications/sendSend (queue for async delivery)
GET/communicationsList with pagination, filters, search
GET/communications/statsGet statistics (by businessId)
GET/communications/pdf-templates/availableList PDF template types
GET/communications/:idGet by ID
PATCH/communications/:idUpdate
DELETE/communications/:idDelete
POST/communications/:id/resendResend a communication
POST/communications/:id/processManual queue bypass (for testing)
GET/communications/:id/attachmentsGet attachments

Webhooks Controller (/webhooks) — Public, no auth

MethodPathDescription
POST/webhooks/sendgrid/eventsSendGrid delivery events
POST/webhooks/twilio/sms-statusTwilio SMS status callbacks
POST/webhooks/twilio/whatsapp-statusTwilio WhatsApp status callbacks
POST/webhooks/twilio/messenger-statusMessenger status (stub)

Channel Adapters

Each adapter implements ICommunicationChannel from domain/communication.types.ts:

ChannelProviderAdapterNotes
EmailSendGridEmailAdapterServiceHTML + text, attachments, tracking
SMSTwilioSmsAdapterServiceE.164 validation, 1600 char limit
WhatsAppTwilioWhatsAppAdapterServiceTemplate + freeform messages
MessengerTwilioNot yet implemented

Environment Variables

VariableRequiredDescription
SENDGRID_API_KEYYes (email)SendGrid API key
SENDGRID_FROM_EMAILNoFrom email (default: noreply@flowpos.com)
TWILIO_ACCOUNT_SIDYes (SMS/WA)Must start with "AC"
TWILIO_AUTH_TOKENYes (SMS/WA)Twilio auth token
TWILIO_PHONE_NUMBERYes (SMS)E.164 format
TWILIO_WHATSAPP_NUMBERYes (WA)E.164 format
TWILIO_MESSAGING_SERVICE_SIDNoAlternative to phone number
API_URLRecommendedFor webhook callback URLs
FRONTEND_URLNoFor links in templates (default: localhost:5173)

Status Lifecycle

PENDING → QUEUED → SENT → DELIVERED → OPENED → CLICKED
↘ FAILED
↘ BOUNCED

Status mapping is centralized in TwilioProvider.mapTwilioStatus() and TwilioProvider.mapSendgridEvent().

Key Design Decisions

  1. Domain types decouple application from HTTP layerSendCommunicationPayload (domain) vs SendCommunicationDto (interfaces)
  2. Repository injected via token"ICommunicationsRepository" enables testing with mock implementations
  3. Attachments stored in DB, excluded from queue — Base64 content is too large for BullMQ payloads; stored in communicationAttachment table and retrieved during send
  4. Rate limiting is in-memory — Token bucket algorithm per provider:channel, not distributed (sufficient for single-instance)
  5. Webhook endpoints are public — Provider webhooks don't support auth tokens; signature verification should be added for production
  6. Invite notification logic is sharedInviteNotificationService eliminates duplication between create/resend handlers