Saltar al contenido principal

Recipient Targeting System - Implementation Checklist

Overview

This checklist guides you through implementing the flexible recipient targeting system for the communication module.


Phase 1: Database Setup ✅

1.1 Run Migration

  • Review migration file: 2025-10-26t00:00:00.000z-communication-recipient-targeting.mjs
  • Backup production database
  • Run migration in development
  • Test rollback (down migration)
  • Run migration in staging
  • Run migration in production

1.2 Verify Tables Created

-- Verify all 4 tables exist
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN (
'communication_recipient_group',
'communication_group_member',
'business_communication_recipient_rule',
'communication_recipient_log'
);

-- Verify enums created
SELECT typname FROM pg_type
WHERE typname IN ('recipient_member_type', 'recipient_targeting_type');

1.3 Test Constraints

  • Test unique constraint on group names per business
  • Test foreign key cascades (delete business → deletes rules)
  • Test foreign key cascades (delete group → deletes members)

Phase 2: Backend - Domain & Repository Layer 📦

2.1 Create Domain Types

File: packages/backend/database/src/types/recipient-targeting.types.ts

export type RecipientMemberType = 'business_user' | 'email' | 'phone' | 'whatsapp';
export type RecipientTargetingType = 'role' | 'group' | 'ad_hoc_email' | 'ad_hoc_phone' | 'ad_hoc_whatsapp';

export interface RecipientGroup {
id: string;
businessId: string;
name: string;
description?: string;
isActive: boolean;
createdAt: Date;
}

export interface GroupMember {
id: string;
groupId: string;
memberType: RecipientMemberType;
businessUserId?: string;
emailAddress?: string;
phoneNumber?: string;
displayName?: string;
isActive: boolean;
}

export interface RecipientRule {
id: string;
businessId: string;
communicationType: string;
channel: string;
targetingType: RecipientTargetingType;
roleName?: string;
groupId?: string;
adHocEmail?: string;
adHocPhone?: string;
adHocWhatsapp?: string;
adHocName?: string;
priority: number;
isActive: boolean;
}

export interface ResolvedRecipient {
type: 'business_user' | 'email' | 'phone' | 'whatsapp';
userId?: string;
contact: string;
name?: string;
source: {
method: 'role' | 'group' | 'ad_hoc' | 'manual';
ruleId?: string;
groupId?: string;
roleName?: string;
};
}

Checklist:

  • Create type definitions
  • Export from index
  • Update database.types.ts with new tables
  • Regenerate database types if using kysely-codegen

2.2 Create Repositories

RecipientGroupRepository

File: apps/backend/src/recipient-groups/infrastructure/recipient-groups.repository.ts

@Injectable()
export class RecipientGroupsRepository {
constructor(@Inject(DATABASE) private readonly db: KyselyDatabase) {}

async create(data: InsertableRecipientGroup): Promise<SelectableRecipientGroup> { }
async findById(id: string): Promise<SelectableRecipientGroup | null> { }
async findByBusinessId(businessId: string): Promise<SelectableRecipientGroup[]> { }
async update(id: string, data: UpdateableRecipientGroup): Promise<void> { }
async delete(id: string): Promise<void> { }

// Member management
async addMember(groupId: string, member: InsertableGroupMember): Promise<SelectableGroupMember> { }
async findGroupMembers(groupId: string): Promise<SelectableGroupMember[]> { }
async removeMember(memberId: string): Promise<void> { }
}

Checklist:

  • Create repository file
  • Implement CRUD methods
  • Implement member management
  • Add proper error handling
  • Add logging
  • Write unit tests

RecipientRuleRepository

File: apps/backend/src/recipient-rules/infrastructure/recipient-rules.repository.ts

@Injectable()
export class RecipientRulesRepository {
constructor(@Inject(DATABASE) private readonly db: KyselyDatabase) {}

async create(data: InsertableRecipientRule): Promise<SelectableRecipientRule> { }
async findById(id: string): Promise<SelectableRecipientRule | null> { }

// Key method: Find all rules for a business + type + channel
async findActiveRules(filters: {
businessId: string;
communicationType: string;
channel: string;
}): Promise<SelectableRecipientRule[]> { }

async update(id: string, data: UpdateableRecipientRule): Promise<void> { }
async delete(id: string): Promise<void> { }
}

Checklist:

  • Create repository file
  • Implement CRUD methods
  • Implement findActiveRules with proper filtering
  • Add proper error handling
  • Add logging
  • Write unit tests

RecipientLogRepository

File: apps/backend/src/recipient-log/infrastructure/recipient-log.repository.ts

@Injectable()
export class RecipientLogRepository {
constructor(@Inject(DATABASE) private readonly db: KyselyDatabase) {}

async logRecipient(data: {
communicationId: string;
recipientType: string;
recipientId?: string;
recipientContact: string;
recipientName?: string;
selectionMethod: string;
selectionSourceId?: string;
selectionDetails?: any;
}): Promise<void> { }

async findByCommunication(communicationId: string): Promise<SelectableRecipientLog[]> { }
}

Checklist:

  • Create repository file
  • Implement logging methods
  • Implement query methods
  • Write unit tests

Phase 3: Backend - Service Layer 🔧

3.1 Create RecipientResolverService

File: apps/backend/src/communications/application/services/recipient-resolver.service.ts

Key Methods:

@Injectable()
export class RecipientResolverService {
async resolveRecipients(
businessId: string,
communicationType: string,
channel: string,
): Promise<ResolvedRecipient[]> { }

private async resolveRule(rule: RecipientRule): Promise<ResolvedRecipient[]> { }
private async resolveRoleRecipients(roleName: string, businessId: string): Promise<ResolvedRecipient[]> { }
private async resolveGroupRecipients(groupId: string): Promise<ResolvedRecipient[]> { }
private deduplicateRecipients(recipients: ResolvedRecipient[]): ResolvedRecipient[] { }
}

Implementation Steps:

  • Create service file
  • Implement resolveRecipients (main method)
  • Implement resolveRule (switch on targeting type)
  • Implement resolveRoleRecipients (query business users by role)
  • Implement resolveGroupRecipients (query group members + join users)
  • Implement deduplication logic (same contact)
  • Add logging at each step
  • Write comprehensive unit tests
  • Write integration tests

Unit Tests:

  • Test role resolution with mock data
  • Test group resolution with mixed member types
  • Test ad-hoc resolution
  • Test deduplication (same email via 2 rules)
  • Test empty rules (no recipients)
  • Test inactive rules (should be skipped)

3.2 Create RecipientGroupsService

File: apps/backend/src/recipient-groups/application/recipient-groups.service.ts

@Injectable()
export class RecipientGroupsService {
async createGroup(data: CreateGroupDto): Promise<RecipientGroup> { }
async getGroupsByBusiness(businessId: string): Promise<RecipientGroup[]> { }
async updateGroup(id: string, data: UpdateGroupDto): Promise<void> { }
async deleteGroup(id: string): Promise<void> { }

async addMember(groupId: string, member: AddMemberDto): Promise<GroupMember> { }
async getGroupMembers(groupId: string): Promise<GroupMember[]> { }
async removeMember(memberId: string): Promise<void> { }
}

Checklist:

  • Create service file
  • Implement all methods
  • Add validation (unique names, valid emails/phones)
  • Add authorization checks (user belongs to business)
  • Write unit tests

3.3 Create RecipientRulesService

File: apps/backend/src/recipient-rules/application/recipient-rules.service.ts

@Injectable()
export class RecipientRulesService {
async createRule(data: CreateRuleDto): Promise<RecipientRule> { }
async getRules(filters: GetRulesDto): Promise<RecipientRule[]> { }
async updateRule(id: string, data: UpdateRuleDto): Promise<void> { }
async deleteRule(id: string): Promise<void> { }

// Preview feature: See who will receive without saving
async previewRecipients(data: PreviewRecipientsDto): Promise<ResolvedRecipient[]> { }
}

Checklist:

  • Create service file
  • Implement all methods
  • Implement preview feature using RecipientResolverService
  • Add validation
  • Add authorization checks
  • Write unit tests

Phase 4: Backend - API Layer 🌐

4.1 Create RecipientGroupsController

File: apps/backend/src/recipient-groups/interfaces/recipient-groups.controller.ts

Endpoints:

@Controller('recipient-groups')
export class RecipientGroupsController {
@Post()
async createGroup(@Body() dto: CreateGroupDto) { }

@Get()
async getGroups(@Query('businessId') businessId: string) { }

@Get(':id')
async getGroupById(@Param('id') id: string) { }

@Put(':id')
async updateGroup(@Param('id') id: string, @Body() dto: UpdateGroupDto) { }

@Delete(':id')
async deleteGroup(@Param('id') id: string) { }

@Post(':id/members')
async addMember(@Param('id') groupId: string, @Body() dto: AddMemberDto) { }

@Get(':id/members')
async getMembers(@Param('id') groupId: string) { }

@Delete(':id/members/:memberId')
async removeMember(@Param('memberId') memberId: string) { }
}

Checklist:

  • Create controller file
  • Create DTOs with validation
  • Implement all endpoints
  • Add authentication guards
  • Add authorization checks (business ownership)
  • Add API documentation (Swagger)
  • Write e2e tests

4.2 Create RecipientRulesController

File: apps/backend/src/recipient-rules/interfaces/recipient-rules.controller.ts

Endpoints:

@Controller('communication-recipient-rules')
export class RecipientRulesController {
@Post()
async createRule(@Body() dto: CreateRuleDto) { }

@Get()
async getRules(@Query() query: GetRulesQuery) { }

@Get(':id')
async getRuleById(@Param('id') id: string) { }

@Put(':id')
async updateRule(@Param('id') id: string, @Body() dto: UpdateRuleDto) { }

@Delete(':id')
async deleteRule(@Param('id') id: string) { }

@Post('preview')
async previewRecipients(@Body() dto: PreviewRecipientsDto) { }
}

Checklist:

  • Create controller file
  • Create DTOs with validation
  • Implement all endpoints
  • Add authentication guards
  • Add authorization checks
  • Add API documentation
  • Write e2e tests

4.3 Create DTOs

CreateGroupDto:

export class CreateGroupDto {
@IsUUID()
businessId: string;

@IsString()
@MinLength(3)
name: string;

@IsOptional()
@IsString()
description?: string;
}

AddMemberDto:

export class AddMemberDto {
@IsEnum(['business_user', 'email', 'phone', 'whatsapp'])
memberType: RecipientMemberType;

@ValidateIf(o => o.memberType === 'business_user')
@IsUUID()
businessUserId?: string;

@ValidateIf(o => o.memberType === 'email')
@IsEmail()
emailAddress?: string;

@ValidateIf(o => o.memberType === 'phone' || o.memberType === 'whatsapp')
@IsPhoneNumber()
phoneNumber?: string;

@IsOptional()
@IsString()
displayName?: string;
}

CreateRuleDto:

export class CreateRuleDto {
@IsUUID()
businessId: string;

@IsEnum(CommunicationType)
communicationType: string;

@IsEnum(['email', 'sms', 'whatsapp'])
channel: string;

@IsEnum(['role', 'group', 'ad_hoc_email', 'ad_hoc_phone', 'ad_hoc_whatsapp'])
targetingType: RecipientTargetingType;

@ValidateIf(o => o.targetingType === 'role')
@IsString()
roleName?: string;

@ValidateIf(o => o.targetingType === 'group')
@IsUUID()
groupId?: string;

// ... other fields
}

Checklist:

  • Create all DTOs
  • Add validation decorators
  • Add conditional validation (ValidateIf)
  • Add API documentation decorators
  • Write validation tests

Phase 5: Update Event Handlers 🔄

5.1 Update OnCreateLowStockAlertHandler

File: apps/backend/src/communications/application/events/on-create-low-stock-alert.handler.ts

Changes:

// OLD:
const businessUsers = await this.businessUsersService.findByBusinessId(businessId);
for (const user of businessUsers) { /* send */ }

// NEW:
const recipients = await this.recipientResolverService.resolveRecipients(
businessId,
'low_stock_alert',
'email'
);

for (const recipient of recipients) {
await this.communicationsService.send({
recipientType: recipient.type,
recipientId: recipient.userId,
recipientContact: recipient.contact,
recipientName: recipient.name,
// ... other fields
});

// Log recipient selection
await this.recipientLogRepository.logRecipient({
communicationId: communication.id,
recipientContact: recipient.contact,
selectionMethod: recipient.source.method,
selectionSourceId: recipient.source.ruleId,
});
}

Checklist:

  • Inject RecipientResolverService
  • Inject RecipientLogRepository
  • Replace hardcoded recipient logic
  • Add recipient logging
  • Test with different rule configurations
  • Test with no rules (fallback behavior?)
  • Update handler documentation

5.2 Update Other Handlers

Repeat for each communication type:

  • Invoice handler
  • Collection notice handler
  • Daily report handler
  • Emergency alert handler
  • Any other communication handlers

Phase 6: Admin UI - Frontend 🎨

6.1 Create Group Management Pages

Pages:

  • /settings/recipient-groups - List all groups
  • /settings/recipient-groups/new - Create new group
  • /settings/recipient-groups/:id - View/edit group details
  • /settings/recipient-groups/:id/members - Manage members

Components:

  • GroupListTable
  • GroupForm
  • MemberListTable
  • AddMemberModal
  • MemberTypeSelector

Features:

  • List groups with member counts
  • Create/edit/delete groups
  • Add/remove members
  • Search/filter groups
  • Validate emails/phone numbers
  • Show member preview (users vs external contacts)

6.2 Create Recipient Rule Configuration Pages

Pages:

  • /settings/communication-rules - Overview of all rules
  • /settings/communication-rules/low-stock-alert - Rules for specific type
  • /settings/communication-rules/configure - Configure rules wizard

Components:

  • RuleConfigurationMatrix (grid: types × channels)
  • RulesList
  • RuleForm
  • RecipientPreview (show who will receive)
  • TargetingTypeSelector (role/group/ad-hoc)

Features:

  • Visual matrix of communication types × channels
  • Configure rules per type/channel
  • Add multiple rules (role + group + ad-hoc)
  • Preview recipients before saving
  • Drag to reorder rules (priority)
  • Enable/disable rules

6.3 Integrate with Existing Settings

Add new sections to business settings:

  • Add "Recipient Groups" menu item
  • Add "Communication Rules" menu item
  • Link from communication config page
  • Show recipient count in communication overview

Phase 7: Testing 🧪

7.1 Unit Tests

  • Repository layer tests (all methods)
  • Service layer tests (all methods)
  • RecipientResolverService tests (critical!)
  • Controller tests (all endpoints)
  • DTO validation tests

7.2 Integration Tests

  • Full recipient resolution flow
  • Role resolution with real database
  • Group resolution with mixed members
  • Deduplication logic
  • Logging recipient selection

7.3 E2E Tests

  • Create group via API
  • Add members to group
  • Create recipient rules
  • Preview recipients
  • Trigger communication (low stock alert)
  • Verify correct recipients received messages
  • Verify recipient logs created

7.4 Manual Testing Scenarios

  • Business A: Send low stock to inventory_manager role only
  • Business B: Send low stock to custom "Purchasing Team" group
  • Business C: Send low stock to role + group + ad-hoc phone
  • Empty rules: What happens? (should have fallback)
  • Inactive rules: Should be skipped
  • Deleted group: Rules should cascade delete
  • Duplicate recipients: Should be deduplicated

Phase 8: Documentation 📚

8.1 Technical Documentation

  • Architecture design document
  • Database schema documentation
  • API documentation (Swagger)
  • Service documentation (JSDoc)
  • Integration guide for new handlers

8.2 User Documentation

  • Admin guide: How to create groups
  • Admin guide: How to configure recipient rules
  • Admin guide: Best practices
  • Video tutorial (optional)

8.3 Developer Documentation

  • Migration guide
  • Code examples
  • Troubleshooting guide

Phase 9: Deployment 🚀

9.1 Pre-Deployment

  • Review all code changes
  • Run full test suite
  • Code review completed
  • Database migration tested in staging
  • Backup plan documented

9.2 Deployment Steps

  • Deploy database migration (off-peak hours)
  • Deploy backend changes
  • Deploy frontend changes
  • Verify migration success
  • Test critical paths

9.3 Post-Deployment

  • Monitor error logs
  • Monitor recipient resolution performance
  • Monitor database query performance
  • Verify communications still working
  • User acceptance testing

9.4 Rollback Plan (If Needed)

  • Database rollback script ready
  • Code rollback plan
  • Communication plan for users

Phase 10: Training & Rollout 👥

10.1 Internal Training

  • Train support team on new features
  • Train sales team for demos
  • Create internal FAQ

10.2 Customer Rollout

  • Announce new feature (release notes)
  • Create default rules for existing businesses
  • Offer migration assistance
  • Collect feedback

Success Criteria ✨

Functional Requirements

  • ✅ Businesses can create custom recipient groups
  • ✅ Groups support mixed member types (users + emails + phones)
  • ✅ Businesses can configure rules per communication type/channel
  • ✅ Rules support role + group + ad-hoc targeting
  • ✅ Multiple rules can be combined
  • ✅ Recipients are deduplicated
  • ✅ Recipient selection is logged for audit

Non-Functional Requirements

  • ✅ Recipient resolution takes < 500ms
  • ✅ No breaking changes to existing communications
  • ✅ Admin UI is intuitive (< 5 min to configure)
  • ✅ System is scalable (handles 100+ groups per business)
  • ✅ Full audit trail for compliance

Timeline Estimate

PhaseEstimated Time
Phase 1: Database1 day
Phase 2: Repositories2-3 days
Phase 3: Services3-4 days
Phase 4: APIs2-3 days
Phase 5: Update Handlers2 days
Phase 6: Frontend UI5-7 days
Phase 7: Testing3-4 days
Phase 8: Documentation2 days
Phase 9: Deployment1 day
Phase 10: Rollout1-2 days
Total22-29 days (1-1.5 sprints)

Notes & Considerations

Performance Optimization

  • Consider caching recipient resolution results (TTL: 5 min)
  • Add database indexes on frequently queried columns
  • Optimize query for resolving group members (reduce N+1)

Edge Cases

  • What if no rules configured? → Fallback to existing behavior (all business users)
  • What if all rules return no recipients? → Log warning, skip sending
  • What if ad-hoc email is invalid? → Validate before saving, skip invalid ones
  • What if user is in multiple groups? → Deduplicate in RecipientResolverService

Future Enhancements

  • Location-based filtering (send only to specific locations)
  • Time-based rules (different recipients at different times)
  • Escalation chains (if no response, send to next group)
  • Smart groups (dynamic based on criteria)
  • Recipient preferences (opt-out per type)