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)
| Method | Path | Description |
|---|---|---|
| POST | /communications | Create a communication record |
| POST | /communications/send | Send (queue for async delivery) |
| GET | /communications | List with pagination, filters, search |
| GET | /communications/stats | Get statistics (by businessId) |
| GET | /communications/pdf-templates/available | List PDF template types |
| GET | /communications/:id | Get by ID |
| PATCH | /communications/:id | Update |
| DELETE | /communications/:id | Delete |
| POST | /communications/:id/resend | Resend a communication |
| POST | /communications/:id/process | Manual queue bypass (for testing) |
| GET | /communications/:id/attachments | Get attachments |
Webhooks Controller (/webhooks) — Public, no auth
| Method | Path | Description |
|---|---|---|
| POST | /webhooks/sendgrid/events | SendGrid delivery events |
| POST | /webhooks/twilio/sms-status | Twilio SMS status callbacks |
| POST | /webhooks/twilio/whatsapp-status | Twilio WhatsApp status callbacks |
| POST | /webhooks/twilio/messenger-status | Messenger status (stub) |
Channel Adapters
Each adapter implements ICommunicationChannel from domain/communication.types.ts:
| Channel | Provider | Adapter | Notes |
|---|---|---|---|
| SendGrid | EmailAdapterService | HTML + text, attachments, tracking | |
| SMS | Twilio | SmsAdapterService | E.164 validation, 1600 char limit |
| Twilio | WhatsAppAdapterService | Template + freeform messages | |
| Messenger | Twilio | — | Not yet implemented |
Environment Variables
| Variable | Required | Description |
|---|---|---|
SENDGRID_API_KEY | Yes (email) | SendGrid API key |
SENDGRID_FROM_EMAIL | No | From email (default: noreply@flowpos.com) |
TWILIO_ACCOUNT_SID | Yes (SMS/WA) | Must start with "AC" |
TWILIO_AUTH_TOKEN | Yes (SMS/WA) | Twilio auth token |
TWILIO_PHONE_NUMBER | Yes (SMS) | E.164 format |
TWILIO_WHATSAPP_NUMBER | Yes (WA) | E.164 format |
TWILIO_MESSAGING_SERVICE_SID | No | Alternative to phone number |
API_URL | Recommended | For webhook callback URLs |
FRONTEND_URL | No | For 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
- Domain types decouple application from HTTP layer —
SendCommunicationPayload(domain) vsSendCommunicationDto(interfaces) - Repository injected via token —
"ICommunicationsRepository"enables testing with mock implementations - Attachments stored in DB, excluded from queue — Base64 content is too large for BullMQ payloads; stored in
communicationAttachmenttable and retrieved during send - Rate limiting is in-memory — Token bucket algorithm per provider:channel, not distributed (sufficient for single-instance)
- Webhook endpoints are public — Provider webhooks don't support auth tokens; signature verification should be added for production
- Invite notification logic is shared —
InviteNotificationServiceeliminates duplication between create/resend handlers