Skip to main content

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.ts
  • on-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​

  1. Implement the patches above
  2. Test each scenario thoroughly
  3. Create UI for configuration matrix
  4. Train admins on using the system
  5. Monitor configuration changes in production
  6. Iterate based on feedback

πŸ†˜ Support​

If you encounter issues during integration:

  1. Check logs for configuration check messages
  2. Verify database migration ran successfully
  3. Ensure module is properly imported
  4. Test with simple scenarios first
  5. Use /can-send endpoint to debug configuration

Last Updated: October 25, 2025 Integration Version: 1.0.0