Skip to main content

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:
    • name
    • email
    • phone
    • address (street, unit, city, state_province, postal_code, country)
    • social
    • website
    • image

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:

  1. Contact phone updated to "(555) 123-4567"
  2. Trigger fires immediately
  3. Account phone updated
  4. All orders with this account (seller/buyer) get updated metadata
  5. All tasks inherit updated order metadata
  6. Client sees new phone everywhere instantly

Use Case 2: Business Rebranding​

Scenario: Client changes business name

Flow:

  1. Contact name changed from "ABC Corp" to "ABC Corporation"
  2. Trigger cascades update to:
    • Account name
    • Order metadata (seller/buyer)
    • Subscription metadata
    • Task order_metadata
  3. Agency websites and instasites flagged for regeneration
  4. Sites rebuild with new business name

Use Case 3: Email Migration​

Scenario: Client migrates to new email domain

Flow:

  1. Contact email updated
  2. Account email updated
  3. Owner user email synchronized
  4. All orders reflect new email in metadata
  5. All tasks have updated email
  6. 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​

  1. Early Exit: Return immediately if account not found
  2. Conditional Updates: Only update orders if has_orders: true
  3. Lean Queries: All queries use .lean() for performance
  4. Parallel Updates: Multiple updateMany calls (not awaited sequentially)
  5. Specific Field Updates: Only update changed fields, not entire documents

Potential Bottlenecks​

  1. High-Volume Updates: Multiple order updates per contact change
  2. Subscription Updates: Can affect many subscriptions for main accounts
  3. 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 } }
);
}

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
💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM