Skip to main content

Recipient Targeting System - Entity Relationship Diagram

High-Level Architecture​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ COMMUNICATION CONFIGURATION β”‚
β”‚ β”‚
β”‚ business_communication_config β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Controls: WHAT + HOW + WHEN β”‚ β”‚
β”‚ β”‚ β€’ communication_type (low_stock_alert) β”‚ β”‚
β”‚ β”‚ β€’ channel (email, sms, whatsapp) β”‚ β”‚
β”‚ β”‚ β€’ timing, rate limits, etc. β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β”‚ Extended by β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ business_communication_recipient_rule β”‚ β”‚
β”‚ β”‚ Controls: WHO receives β”‚ β”‚
β”‚ β”‚ β€’ targeting_type (role/group/ad_hoc) β”‚ β”‚
β”‚ β”‚ β€’ role_name OR group_id OR ad_hoc_* β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Detailed Entity Relationship Diagram​

                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ business β”‚
│──────────────│
β”‚ id (PK) β”‚
β”‚ name β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚ β”‚
β”‚ 1:N β”‚ 1:N β”‚ 1:N
β–Ό β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ business_user β”‚ β”‚ business_ β”‚ β”‚ communication_ β”‚
│─────────────────────│ β”‚ communication_ β”‚ β”‚ recipient_group β”‚
β”‚ id (PK) β”‚ β”‚ config β”‚ │─────────────────────│
β”‚ business_id (FK) β”‚ │─────────────────│ β”‚ id (PK) β”‚
β”‚ user_id (FK) β”‚ β”‚ id (PK) β”‚ β”‚ business_id (FK) β”‚
β”‚ unique_role_name β”‚ β”‚ business_id (FK)β”‚ β”‚ name β”‚
β”‚ is_active β”‚ β”‚ comm_type β”‚ β”‚ description β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ channel β”‚ β”‚ is_active β”‚
β”‚ β”‚ is_enabled β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚ is_automatic β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ 1:N
β”‚ β”‚ β”‚
β”‚ β”‚ 1:N β–Ό
β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ group_member β”‚
β”‚ β”‚ business_ β”‚ │──────────────────────│
β”‚ β”‚ communication_ β”‚ β”‚ id (PK) β”‚
β”‚ β”‚ recipient_rule β”‚ β”‚ group_id (FK) β”‚
β”‚ │──────────────────────│ β”‚ member_type [ENUM] β”‚
β”‚ β”‚ id (PK) β”‚ β”‚ β€’ business_user β”‚
β”‚ β”‚ business_id (FK) β”‚ β”‚ β€’ email β”‚
β”‚ β”‚ communication_type β”‚ β”‚ β€’ phone β”‚
β”‚ β”‚ channel β”‚ β”‚ β€’ whatsapp β”‚
β”‚ β”‚ targeting_type [ENUM]β”‚ β”‚ β”‚
β”‚ β”‚ β€’ role β”‚ β”‚ Polymorphic: β”‚
β”‚ β”‚ β€’ group β”‚ β”‚ business_user_id (FK)β”‚β—„β”€β”€β”€β”˜
β”‚ β”‚ β€’ ad_hoc_email β”‚ β”‚ email_address β”‚
β”‚ β”‚ β€’ ad_hoc_phone β”‚ β”‚ phone_number β”‚
β”‚ β”‚ β€’ ad_hoc_whatsapp β”‚ β”‚ display_name β”‚
β”‚ β”‚ β”‚ β”‚ is_active β”‚
β”‚ β”‚ Polymorphic: β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚ role_name β”‚
β”‚ β”‚ group_id (FK) β”‚β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚ ad_hoc_email β”‚
β”‚ β”‚ ad_hoc_phone β”‚
β”‚ β”‚ ad_hoc_whatsapp β”‚
β”‚ β”‚ priority β”‚
β”‚ β”‚ is_active β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚
β”‚ RecipientResolverService β”‚
β”‚ ───────────────────────── β”‚
β”‚ Resolves rules to actual β”‚
β”‚ recipients: β”‚
β”‚ β”‚
β”‚ 1. Query active rules β”‚
β”‚ 2. For each rule: β”‚
β”‚ β€’ role β†’ get users β”‚
β”‚ β€’ group β†’ get members β”‚
β”‚ β€’ ad_hoc β†’ use directly β”‚
β”‚ 3. Deduplicate β”‚
β”‚ 4. Return final list β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”‚ Returns list of
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ResolvedRecipient[] β”‚
│──────────────────────│
β”‚ type β”‚
β”‚ userId β”‚
β”‚ contact (email/phone)β”‚
β”‚ name β”‚
β”‚ source (how selected)β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”‚ Used by
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ CommunicationsServiceβ”‚
β”‚ (Send messages) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”‚ Creates
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ communication β”‚ β”‚ communication_ β”‚
│──────────────────────│ 1:N β”‚ recipient_log β”‚
β”‚ id (PK) │───────►│──────────────────────│
β”‚ business_id β”‚ β”‚ id (PK) β”‚
β”‚ type β”‚ β”‚ communication_id (FK)β”‚
β”‚ channel β”‚ β”‚ recipient_contact β”‚
β”‚ recipient_contact β”‚ β”‚ recipient_name β”‚
β”‚ status β”‚ β”‚ selection_method β”‚
β”‚ ... β”‚ β”‚ selection_source_id β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ selected_at β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
(Audit Trail)

Table Relationships Explained​

1. business β†’ communication_recipient_group (1:N)​

Each business can create multiple recipient groups.

Example:

  • Business A has: "Finance Team", "Purchasing Team", "Emergency Team"

2. communication_recipient_group β†’ communication_group_member (1:N)​

Each group has multiple members (polymorphic).

Example: "Finance Team" group contains:

  • Member 1: business_user β†’ John Doe (internal)
  • Member 2: business_user β†’ Jane Smith (internal)
  • Member 3: email β†’ cpa@firm.com (external)
  • Member 4: phone β†’ +1234567890 (external)

3. business_user ← communication_group_member (Optional FK)​

Group members can reference business users.

When to use:

  • member_type = 'business_user' β†’ Set business_user_id
  • member_type = 'email' β†’ Set email_address
  • member_type = 'phone' β†’ Set phone_number

4. business_communication_config ← business_communication_recipient_rule​

Rules extend configs to specify recipients.

Relationship:

  • Config says: "Low stock alerts via email are enabled"
  • Rules say: "Send to inventory_manager role + Purchasing Team group"

Note: communication_config_id is optional. If null, rules apply to all channels for that type.


5. communication_recipient_group ← business_communication_recipient_rule​

Rules can target groups.

Example:

-- Rule targeting a group
{
targeting_type: 'group',
group_id: 'purchasing-team-uuid'
}

6. communication β†’ communication_recipient_log (1:N)​

Each communication logs all recipients selected.

Example: For communication ID abc-123, log entries:


Data Flow Example: Low Stock Alert​

1. Event: Low Stock Alert Created
└─ Alert ID: alert-123
Business ID: business-456

2. Handler: OnCreateLowStockAlertHandler
└─ Calls: RecipientResolverService.resolveRecipients(
business-456,
'low_stock_alert',
'email'
)

3. RecipientResolverService queries recipient rules:

Query: SELECT * FROM business_communication_recipient_rule
WHERE business_id = 'business-456'
AND communication_type = 'low_stock_alert'
AND channel = 'email'
AND is_active = true

Results:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Rule 1 β”‚ targeting_type β”‚ role β”‚ β”‚
β”‚ β”‚ role_name β”‚ inventory_mgr β”‚ β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Rule 2 β”‚ targeting_type β”‚ group β”‚ β”‚
β”‚ β”‚ group_id β”‚ purchase-team β”‚ β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Rule 3 β”‚ targeting_type β”‚ ad_hoc_email β”‚ β”‚
β”‚ β”‚ ad_hoc_email β”‚ supplier@... β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

4. Resolve Rule 1 (role = 'inventory_manager'):

Query: SELECT u.id, u.email, u.full_name
FROM business_user bu
JOIN user u ON u.id = bu.user_id
WHERE bu.business_id = 'business-456'
AND bu.unique_role_name = 'inventory_manager'
AND bu.is_active = true

Results:
- john@company.com (John Doe)
- jane@company.com (Jane Smith)

5. Resolve Rule 2 (group = 'purchase-team'):

Query: SELECT gm.*, u.email, u.full_name
FROM communication_group_member gm
LEFT JOIN business_user bu ON bu.id = gm.business_user_id
LEFT JOIN user u ON u.id = bu.user_id
WHERE gm.group_id = 'purchase-team'
AND gm.is_active = true

Results:
- staff1@company.com (Staff One) [business_user]
- staff2@company.com (Staff Two) [business_user]
- supplier@vendor.com (Main Supplier) [email]

6. Resolve Rule 3 (ad_hoc_email):

Direct use:
- supplier2@vendor.com (Backup Supplier)

7. Deduplicate recipients:

Combined list (6 total):
βœ“ john@company.com
βœ“ jane@company.com
βœ“ staff1@company.com
βœ“ staff2@company.com
βœ“ supplier@vendor.com
βœ“ supplier2@vendor.com

8. Send communications:

FOR EACH recipient:
- Create communication record
- Queue for sending
- Log recipient selection

9. Log recipient selections:

INSERT INTO communication_recipient_log:
- john@company.com (method: role, source: rule-1)
- jane@company.com (method: role, source: rule-1)
- staff1@company.com (method: group, source: rule-2)
- staff2@company.com (method: group, source: rule-2)
- supplier@vendor.com (method: group, source: rule-2)
- supplier2@vendor.com (method: ad_hoc, source: rule-3)

10. Result: 6 emails sent βœ…

Polymorphic Relationships​

Group Members (Polymorphic by Type)​

member_type = 'business_user'
β”œβ”€ business_user_id β†’ NOT NULL
β”œβ”€ email_address β†’ NULL
└─ phone_number β†’ NULL

member_type = 'email'
β”œβ”€ business_user_id β†’ NULL
β”œβ”€ email_address β†’ NOT NULL
└─ phone_number β†’ NULL

member_type = 'phone'
β”œβ”€ business_user_id β†’ NULL
β”œβ”€ email_address β†’ NULL
└─ phone_number β†’ NOT NULL

Recipient Rules (Polymorphic by Targeting Type)​

targeting_type = 'role'
β”œβ”€ role_name β†’ NOT NULL
β”œβ”€ group_id β†’ NULL
└─ ad_hoc_* β†’ NULL

targeting_type = 'group'
β”œβ”€ role_name β†’ NULL
β”œβ”€ group_id β†’ NOT NULL
└─ ad_hoc_* β†’ NULL

targeting_type = 'ad_hoc_email'
β”œβ”€ role_name β†’ NULL
β”œβ”€ group_id β†’ NULL
β”œβ”€ ad_hoc_email β†’ NOT NULL
└─ other ad_hoc_* β†’ NULL

Indexes for Performance​

Critical Query Paths​

1. Resolve Rules for Business + Type + Channel​

-- Index on business_communication_recipient_rule
CREATE INDEX idx_recipient_rule_type_channel
ON business_communication_recipient_rule (business_id, communication_type, channel);

2. Resolve Role to Users​

-- Index on business_user
CREATE INDEX idx_business_user_role
ON business_user (business_id, unique_role_name, is_active);

3. Resolve Group Members​

-- Index on communication_group_member
CREATE INDEX idx_group_member_active
ON communication_group_member (group_id, is_active);

4. Query Recipient Logs​

-- Index on communication_recipient_log
CREATE INDEX idx_recipient_log_communication
ON communication_recipient_log (communication_id);

Summary​

The recipient targeting system uses:

  • 4 new tables for flexible recipient configuration
  • Polymorphic relationships for extensibility
  • Service-based resolution for clean separation of concerns
  • Audit logging for compliance and debugging

The design allows businesses to configure complex recipient scenarios without code changes while maintaining full audit trails and performance through proper indexing.