Contacts Trigger - CRM Synchronization
Overview
File: queue-manager/triggers/contacts.js
Function: syncAccountWithCRM()
Purpose: Real-time synchronization of contact data to accounts, orders, tasks, and sites
The contacts trigger ensures that when CRM contact information changes, all related business entities are immediately updated to maintain data consistency across the platform.
Trigger Configuration
Watched Collection
- Collection:
Contacts - Events: Insert, Replace, Update
- Watched Fields:
nameemailphoneaddress(street, unit, city, state_province, postal_code, country)socialwebsiteimage
Match Conditions
const matchConditions = {
$or: [
{ operationType: { $in: ['insert', 'replace'] } },
{
$and: [
{ operationType: 'update' },
{
$or: [
{ 'updateDescription.updatedFields.name': { $exists: true } },
{ 'updateDescription.updatedFields.email': { $exists: true } },
{ 'updateDescription.updatedFields.phone': { $exists: true } },
{ 'updateDescription.updatedFields.address': { $exists: true } },
{ 'updateDescription.updatedFields.social': { $exists: true } },
{ 'updateDescription.updatedFields.website': { $exists: true } },
{ 'updateDescription.updatedFields.image': { $exists: true } },
],
},
],
},
],
'fullDocument.account': { $exists: true },
};
Data Flow
sequenceDiagram
participant CRM as Internal API/CRM
participant CONTACT as Contacts Collection
participant TRIGGER as contacts.js Trigger
participant ACCOUNT as Accounts Collection
participant ORDER as Orders Collection
participant TASK as Projects Tasks
participant PULSE as Projects Pulses
participant USER as Users Collection
participant SITE as Agency Websites
participant INSTA as Instasites
CRM->>CONTACT: Update contact info
CONTACT->>TRIGGER: Change stream event
TRIGGER->>ACCOUNT: Update account fields
alt Account has orders
TRIGGER->>ORDER: Update seller metadata
TRIGGER->>ORDER: Update buyer metadata
end
alt Email changed
TRIGGER->>USER: Update owner email
TRIGGER->>TASK: Update order_metadata
end
alt Name changed
TRIGGER->>TASK: Update order_metadata
TRIGGER->>ORDER: Update metadata
end
TRIGGER->>SITE: Set business_details_updated
TRIGGER->>INSTA: Set business_details_updated
Processing Logic
Step 1: Build Update Payload
const updatePayload = {
name: fullDocument.name,
email: fullDocument.email,
phone: fullDocument.phone || '',
address: fullDocument.address || {},
social: fullDocument.social || {},
website: fullDocument.website || '',
logo: fullDocument.image,
};
Step 2: Update Account
Updates the associated account with all contact information:
const acc = await Account.findOneAndUpdate(
{ _id: fullDocument.account },
{ $set: updatePayload },
{ new: true },
)
.lean()
.exec();
Step 3: Update Orders (if has_orders)
If the account has orders, update order metadata:
const accountMetadata = {
id: acc._id,
name: acc.name,
email: acc.email,
phone: acc.phone || '',
image: acc.logo,
};
// Update seller orders
await Order.updateMany(
{ seller_account: acc._id },
{ $set: { 'metadata.seller': accountMetadata } },
);
// Update buyer orders
await Order.updateMany({ buyer_account: acc._id }, { $set: { 'metadata.buyer': accountMetadata } });
Step 4: Email Change Cascades
When email changes, additional updates occur:
if (updates.email) {
// Update owner user
let owner = await User.findOne({
account: fullDocument.account,
is_owner: true,
});
if (owner && owner.email != updates.email) {
await User.updateOne({ _id: owner._id }, { $set: { email: updates.email } });
}
// Update tasks metadata
await ProjectsTasks.updateMany(
{ 'order_metadata.seller.id': acc._id },
{ $set: { 'order_metadata.seller.email': updates.email } },
);
await ProjectsTasks.updateMany(
{ 'order_metadata.buyer.id': acc._id },
{ $set: { 'order_metadata.buyer.email': updates.email } },
);
}
Step 5: Name Change Cascades
When name changes, propagate to all related entities:
if (updates.name) {
// Update tasks
await ProjectsTasks.updateMany(
{ 'order_metadata.seller.id': acc._id },
{ $set: { 'order_metadata.seller.name': updates.name } },
);
// Update orders
const nameSet = { 'metadata.account_name': updates.name };
if (acc.main) {
nameSet['metadata.main_account_name'] = updates.name;
}
await Order.updateMany({ seller_account: acc._id }, { $set: nameSet });
await Order.updateMany({ buyer_account: acc._id }, { $set: nameSet });
// Update subscriptions
if (acc.main) {
// Main account: update main_account_name everywhere
await Subscription.updateMany(
{
$or: [{ account: acc._id }, { 'metadata.main_account_id': acc._id }],
},
{ $set: { 'metadata.main_account_name': updates.name } },
);
// Update account_name where this is the account_id
await Subscription.updateMany(
{ 'metadata.account_id': acc._id },
{ $set: { 'metadata.account_name': updates.name } },
);
} else {
// Sub account: update sub_account_name
await Subscription.updateMany(
{ 'metadata.sub_account_id': acc._id },
{ $set: { 'metadata.sub_account_name': updates.name } },
);
}
}
Step 6: Sync Sites
Flag agency websites and instasites for business detail updates:
await AgencyWebsite.updateMany(
{ account_id: fullDocument.account },
{ $set: { business_details_updated: true } },
);
await Instasite.updateMany(
{
business_id: acc.business,
status: { $nin: ['PENDING', 'FAILED', 'PURGED'] },
},
{ $set: { business_details_updated: true } },
);
Collections Updated
Primary Updates
- Accounts - All contact fields synchronized
Conditional Updates (has_orders)
- Orders - seller/buyer metadata
- Subscriptions - account_name, main_account_name, sub_account_name
Field-Specific Updates
- Users - Owner email (when email changes)
- Projects Tasks - order_metadata.seller/buyer
- Projects Pulses - order_metadata.seller/buyer
- Agency Websites - business_details_updated flag
- Instasites - business_details_updated flag
Use Cases
Use Case 1: Client Phone Number Update
Scenario: Sales rep updates client phone in CRM
Flow:
- Contact phone updated to "(555) 123-4567"
- Trigger fires immediately
- Account phone updated
- All orders with this account (seller/buyer) get updated metadata
- All tasks inherit updated order metadata
- Client sees new phone everywhere instantly
Use Case 2: Business Rebranding
Scenario: Client changes business name
Flow:
- Contact name changed from "ABC Corp" to "ABC Corporation"
- Trigger cascades update to:
- Account name
- Order metadata (seller/buyer)
- Subscription metadata
- Task order_metadata
- Agency websites and instasites flagged for regeneration
- Sites rebuild with new business name
Use Case 3: Email Migration
Scenario: Client migrates to new email domain
Flow:
- Contact email updated
- Account email updated
- Owner user email synchronized
- All orders reflect new email in metadata
- All tasks have updated email
- Email communications use new address
Error Handling
Account Not Found
if (!acc) {
logger.log({
initiator: 'queue-manager/triggers/contacts',
message: 'Account not found for update',
});
return;
}
Site Sync Errors
try {
await AgencyWebsite.updateMany(/* ... */);
await Instasite.updateMany(/* ... */);
} catch (error) {
logger.error({
initiator: 'queue-manager/triggers/contacts',
error,
message: 'Error updating sites',
});
}
Main Update Errors
try {
const acc = await Account.findOneAndUpdate(/* ... */);
// ... cascade updates ...
} catch (error) {
logger.error({
initiator: 'queue-manager/triggers/contacts',
error,
message: 'Error updating account with CRM data',
});
}
Performance Considerations
Optimizations
- Early Exit: Return immediately if account not found
- Conditional Updates: Only update orders if
has_orders: true - Lean Queries: All queries use
.lean()for performance - Parallel Updates: Multiple
updateManycalls (not awaited sequentially) - Specific Field Updates: Only update changed fields, not entire documents
Potential Bottlenecks
- High-Volume Updates: Multiple order updates per contact change
- Subscription Updates: Can affect many subscriptions for main accounts
- Site Regeneration: Agency websites/instasites may queue rebuilds
Main Account vs Sub-Account Logic
Main Account Name Change
if (acc.main) {
// Update main_account_name everywhere this account participates
await Subscription.updateMany(
{
$or: [{ account: acc._id }, { 'metadata.main_account_id': acc._id }],
},
{ $set: { 'metadata.main_account_name': updates.name } },
);
}
Sub-Account Name Change
else {
// Update sub_account_name where this is the sub account
await Subscription.updateMany(
{ 'metadata.sub_account_id': acc._id },
{ $set: { 'metadata.sub_account_name': updates.name } }
);
}
Related Triggers
Downstream Triggers
- order.js: Orders updated by this trigger may fire order triggers
- users.js: User email updates may fire user triggers
Complementary Triggers
- accounts.js: Handles account-specific changes (manager)
- projects.js: Ensures tasks/pulses get metadata updates
Monitoring
Key Metrics
- Trigger Frequency: How often contacts are updated
- Cascade Volume: Number of collections updated per trigger
- Error Rate: Failed updates to accounts/orders/tasks
- Query Performance: Time spent in database operations
Logging
logger.log({
initiator: 'queue-manager/triggers/contacts',
message: 'Account not found for update',
});
logger.error({
initiator: 'queue-manager/triggers/contacts',
error,
message: 'Error updating account with CRM data',
});
Best Practices
1. Field-Specific Logic
Only execute expensive cascades when relevant fields change:
if (updates.email) {
// Email-specific cascades
}
if (updates.name) {
// Name-specific cascades
}
2. Avoid Unnecessary Updates
Check for actual changes before updating:
if (owner && owner.email != updates.email) {
await User.updateOne(/* ... */);
}
3. Handle Missing Data Gracefully
phone: fullDocument.phone || '',
address: fullDocument.address || {},
Summary
The contacts trigger is the primary synchronization mechanism for CRM data, ensuring that contact changes immediately propagate to accounts, orders, tasks, subscriptions, and sites. It maintains data consistency across 8+ collections with intelligent cascading logic for main/sub accounts and conditional updates based on field changes.
Key Characteristics:
- ⚡ Immediate synchronization on contact changes
- 🔄 Cascades to 8+ collections
- 🎯 Field-specific update logic
- 🛡️ Graceful error handling
- 🏢 Main/sub-account awareness