Business Communication Configuration - Integration Patch
π Overviewβ
This document describes how to integrate the Business Communication Configuration system into the existing CommunicationsService.
π§ Required Changesβ
1. Update CommunicationsModuleβ
File: apps/backend/src/communications/communications.module.ts
Add the configuration module as an import:
import { BusinessCommunicationConfigModule } from '../business-communication-config/business-communication-config.module';
@Module({
imports: [
DatabaseModule,
CommunicationTemplatesModule,
CommunicationPreferencesModule,
CommunicationQueueModule,
BusinessesModule,
BusinessCommunicationConfigModule, // β
ADD THIS
],
// ...
})
export class CommunicationsModule {}
2. Update CommunicationsServiceβ
File: apps/backend/src/communications/application/communications.service.ts
Step 2a: Add Configuration Service to Constructorβ
import { BusinessCommunicationConfigService } from '../../business-communication-config/application/business-communication-config.service';
@Injectable()
export class CommunicationsService {
private readonly logger = new Logger(CommunicationsService.name);
constructor(
private readonly repository: CommunicationsRepository,
private readonly templateService: CommunicationTemplatesService,
private readonly preferencesService: CommunicationPreferencesService,
private readonly queueService: CommunicationQueueService,
private readonly channelFactory: ChannelFactoryService,
private readonly businessConfigService: BusinessCommunicationConfigService, // β
ADD THIS
) {}
// ...
}
Step 2b: Add Configuration Check in send() Methodβ
Location: Right at the start of the send() method (around line 87)
Before:
async send(data: SendCommunicationDto): Promise<SelectableCommunication> {
const businessId = data.businessId || "system";
const userId = data.createdBy || "system";
// 1. Check preferences (if recipient exists)
if (businessId && data.recipientId) {
// ... preference check
}
// ... rest of method
}
After:
async send(data: SendCommunicationDto): Promise<SelectableCommunication> {
const businessId = data.businessId || "system";
const userId = data.createdBy || "system";
// π 0. Check business communication configuration
if (businessId !== "system") {
this.logger.debug(
`Checking business config for business=${businessId}, type=${data.type}, channel=${data.channel}`
);
const configCheck = await this.businessConfigService.canSend(
businessId,
data.type,
data.channel,
{
recipientId: data.recipientId,
amount: data.templateVariables?.amount
? parseFloat(String(data.templateVariables.amount))
: undefined,
metadata: data.templateVariables,
}
);
if (!configCheck.allowed) {
this.logger.warn(
`Communication blocked by business configuration: ${configCheck.reason}`
);
throw new Error(
`Communication not allowed: ${configCheck.reason}`
);
}
this.logger.debug(`Business configuration check passed`);
// Get template override from business config (if any)
const templateOverride = await this.businessConfigService.getTemplateOverride(
businessId,
data.type,
data.channel
);
if (templateOverride && !data.templateId) {
this.logger.log(`Using template override from business config: ${templateOverride}`);
data.templateId = templateOverride;
}
// Get send delay from business config
const sendDelay = await this.businessConfigService.getSendDelay(
businessId,
data.type,
data.channel
);
if (sendDelay > 0) {
this.logger.log(`Send delayed by ${sendDelay} minutes per business config`);
// Schedule for later instead of sending now
return this.queueService.enqueue({
communicationId: null, // Will be set after creating communication record
priority: 'normal',
scheduledAt: new Date(Date.now() + sendDelay * 60 * 1000),
payload: data,
});
}
}
// 1. Check preferences (if recipient exists)
if (businessId && data.recipientId) {
const canSend = await this.preferencesService.canSend(
data.recipientType,
data.recipientId,
businessId,
data.channel,
data.type,
);
if (!canSend) {
throw new Error(
"Recipient has opted out or disabled this channel for this type of communication",
);
}
}
// ... rest of method continues unchanged
}
3. Update Event Handlers to Check Auto-Sendβ
Example: apps/backend/src/communications/application/events/on-create-invite.handler.ts
Before:
async handle(event: OnCreateInviteEvent) {
// Always send invitation email
await this.sendInvitationEmail(event.invite);
}
After:
async handle(event: OnCreateInviteEvent) {
const businessId = event.invite.businessId;
// Check if auto-send is enabled for invitations
const shouldAutoSend = await this.businessConfigService.shouldAutoSend(
businessId,
'invitation',
'email'
);
if (!shouldAutoSend) {
this.logger.log(
`Auto-send disabled for invitation emails in business ${businessId}. Skipping.`
);
return;
}
// Send invitation email
await this.sendInvitationEmail(event.invite);
}
Apply this pattern to all event handlers:
on-create-invite.handler.tson-create-sale.handler.ts(future)on-payment-received.handler.ts(future)
4. Add Configuration Check to SendInvoiceUseCaseβ
File: apps/backend/src/communications/application/use-cases/send-invoice.use-case.ts
Location: In the execute() method, before calling communicationsService.send()
async execute(params: SendInvoiceParams): Promise<void> {
// ... existing validation code ...
// π Check if this channel is enabled for invoices
const configCheck = await this.businessConfigService.canSend(
params.businessId,
'invoice',
params.channel
);
if (!configCheck.allowed) {
this.logger.warn(
`Cannot send invoice via ${params.channel}: ${configCheck.reason}`
);
throw new Error(`Invoice sending not configured: ${configCheck.reason}`);
}
// ... rest of method continues
}
5. Update Business Onboardingβ
File: apps/backend/src/businesses/application/businesses.service.ts
Location: In the create() or onBusinessCreated() method
import { BusinessCommunicationConfigService } from '../../business-communication-config/application/business-communication-config.service';
@Injectable()
export class BusinessesService {
constructor(
// ... existing dependencies
private readonly businessConfigService: BusinessCommunicationConfigService,
) {}
async create(data: CreateBusinessDto): Promise<Business> {
// Create business
const business = await this.repository.create(data);
// π Initialize default communication configurations
this.logger.log(`Initializing communication config for business ${business.id}`);
await this.businessConfigService.createDefaultConfigurations(
business.id,
data.createdBy
);
return business;
}
}
π§ͺ Testing the Integrationβ
Test 1: Configuration Blocks Sendβ
// Disable invoices via WhatsApp
POST /business-communication-config
{
"businessId": "test-business-id",
"communicationType": "invoice",
"channel": "whatsapp",
"isEnabled": false,
"isAutomatic": false
}
// Try to send invoice via WhatsApp
POST /communications/send
{
"businessId": "test-business-id",
"type": "invoice",
"channel": "whatsapp",
// ... other fields
}
// Expected: Error "Communication not allowed: invoice via whatsapp is disabled"
Test 2: Time Window Restrictionβ
// Configure email to only send 9am-5pm
POST /business-communication-config
{
"businessId": "test-business-id",
"communicationType": "invoice",
"channel": "email",
"isEnabled": true,
"sendTimeWindowStart": "09:00",
"sendTimeWindowEnd": "17:00",
"timezone": "America/Mexico_City"
}
// Try to send at 8pm
// Expected: Error "Outside allowed time window"
Test 3: Auto-Send Disabledβ
// Disable auto-send for invoices
POST /business-communication-config
{
"businessId": "test-business-id",
"communicationType": "invoice",
"channel": "email",
"isEnabled": true,
"isAutomatic": false // β οΈ Not automatic
}
// Create a sale (which would trigger OnCreateSale event)
POST /sales
{ /* sale data */ }
// Expected: Invoice NOT sent automatically (manual send required)
Test 4: Conditional Sendingβ
// Only send SMS if amount >= $100
POST /business-communication-config
{
"businessId": "test-business-id",
"communicationType": "payment_link",
"channel": "sms",
"isEnabled": true,
"conditions": {
"min_amount": 100
}
}
// Try to send for $50 invoice
POST /communications/send
{
"type": "payment_link",
"channel": "sms",
"templateVariables": { "amount": 50 }
}
// Expected: Error "Custom conditions not met: Amount 50 is below minimum 100"
π Verification Checklistβ
After implementing the changes:
- Business configuration is checked before sending
- Time windows are respected
- Rate limits are enforced
- Custom conditions are evaluated
- Template overrides work
- Send delays work
- Auto-send can be disabled
- New businesses get default configs
- Event handlers respect auto-send setting
- Configuration changes are audited
- Errors are logged properly
- API endpoints work
- Bulk updates work
- UI displays matrix correctly (future)
π― Next Stepsβ
- Implement the patches above
- Test each scenario thoroughly
- Create UI for configuration matrix
- Train admins on using the system
- Monitor configuration changes in production
- Iterate based on feedback
π Supportβ
If you encounter issues during integration:
- Check logs for configuration check messages
- Verify database migration ran successfully
- Ensure module is properly imported
- Test with simple scenarios first
- Use
/can-sendendpoint to debug configuration
Last Updated: October 25, 2025 Integration Version: 1.0.0