Skip to main content

Accounts Trigger - Account Manager Tracking

Overview

File: queue-manager/triggers/accounts.js
Function: monitorAccountManagerChange()
Purpose: Automatically update project pulse metadata when account managers change

When an account's manager field is updated, this trigger ensures that all related project pulses reflect the new manager information, maintaining consistency across customer engagement tracking.

Trigger Configuration

Watched Collection

  • Collection: Accounts
  • Event: Update only
  • Watched Field: manager

Match Conditions

const matchConditions = {
$and: [
{ operationType: 'update' },
{ 'updateDescription.updatedFields.manager': { $exists: true } },
],
};

Key Feature: Highly specific - only fires when the manager field itself changes, not on any account update.

Data Flow

sequenceDiagram
participant API as Internal API
participant ACCOUNT as Accounts Collection
participant TRIGGER as accounts.js Trigger
participant USER as Users Collection
participant PULSE as Projects Pulses

API->>ACCOUNT: Update account.manager
ACCOUNT->>TRIGGER: Change stream event
TRIGGER->>TRIGGER: Extract manager ID
TRIGGER->>USER: Query manager details
USER-->>TRIGGER: Return name, email
TRIGGER->>PULSE: Update 6 pulse types
Note over PULSE: account_churn_risk<br/>new_subscription_inquiry<br/>quarterly_checkin<br/>schedule_onboarding<br/>loyalty_program_milestone<br/>review_request

Processing Logic

Step 1: Extract Manager ID

Handle both insert and update operations:

const managerId =
changeEvent.operationType === 'update' ? changeEvent.updatedFields.manager : fullDocument.manager;

if (!managerId) return;

Step 2: Log Manager Change

logger.log({
initiator: 'queue-manager/triggers/accounts',
message: `Account ${fullDocument._id} manager changed to: ${managerId}`,
});

Step 3: Query Manager Details

const manager = await User.findById(managerId).select('_id name email').lean();

Step 4: Update Project Pulses

If manager found, update all relevant pulse types:

const PULSE_TYPES = [
'account_churn_risk',
'new_subscription_inquiry',
'quarterly_checkin',
'schedule_onboarding',
'loyalty_program_milestone',
'review_request',
];

if (manager) {
await ProjectPulse.updateMany(
{
type: { $in: PULSE_TYPES },
account_id: fullDocument._id,
},
{
$set: {
'metadata.dashclicks_team.manager': {
id: manager._id,
name: manager.name,
email: manager.email,
},
},
},
);

logger.log({
initiator: 'queue-manager/triggers/accounts',
message: `Updated ProjectPulse metadata for account ${fullDocument._id} with new manager: ${manager.name}`,
});
}

Complete Code

const ProjectPulse = require('../models/projects-pulse');
const Account = require('../models/account');
const logger = require('../utilities/logger');
const User = require('../models/user');
const { startChangeStream } = require('../common/changeStream');

exports.monitorAccountManagerChange = () => {
const matchConditions = {
$and: [
{ operationType: 'update' },
{ 'updateDescription.updatedFields.manager': { $exists: true } },
],
};

startChangeStream(Account, matchConditions, async changeEvent => {
const fullDocument = changeEvent.data;
const managerId =
changeEvent.operationType === 'update'
? changeEvent.updatedFields.manager
: fullDocument.manager;

if (!managerId) return;

logger.log({
initiator: 'queue-manager/triggers/accounts',
message: `Account ${fullDocument._id} manager changed to: ${managerId}`,
});

const PULSE_TYPES = [
'account_churn_risk',
'new_subscription_inquiry',
'quarterly_checkin',
'schedule_onboarding',
'loyalty_program_milestone',
'review_request',
];

try {
const manager = await User.findById(managerId).select('_id name email').lean();

if (manager) {
await ProjectPulse.updateMany(
{
type: { $in: PULSE_TYPES },
account_id: fullDocument._id,
},
{
$set: {
'metadata.dashclicks_team.manager': {
id: manager._id,
name: manager.name,
email: manager.email,
},
},
},
);

logger.log({
initiator: 'queue-manager/triggers/accounts',
message: `Updated ProjectPulse metadata for account ${fullDocument._id} with new manager: ${manager.name}`,
});
}
} catch (error) {
logger.error({
initiator: 'queue-manager/triggers/accounts',
error,
message: 'Error updating ProjectPulse metadata',
});
}
});
};

Affected Pulse Types

1. account_churn_risk

Purpose: Track accounts at risk of cancellation
Why Manager Matters: New manager needs visibility into at-risk accounts
Created By: services/projects/accountChurnRisk.js

2. new_subscription_inquiry

Purpose: Track new customer inquiries
Why Manager Matters: Manager should handle onboarding communication
Created By: Projects module services

3. quarterly_checkin

Purpose: Quarterly customer engagement pulses
Why Manager Matters: Manager responsible for check-in meetings
Created By: services/projects/quarterlyCheckInPulse.js

4. schedule_onboarding

Purpose: New customer onboarding scheduling
Why Manager Matters: Manager conducts onboarding calls
Created By: Onboarding workflows

5. loyalty_program_milestone

Purpose: Track MRR tier achievements
Why Manager Matters: Manager recognizes and rewards loyalty
Created By: services/projects/loyaltyMilestone.js

6. review_request

Purpose: Automated review request generation
Why Manager Matters: Manager name may appear in review requests
Created By: services/projects/reviewRequestPulse.js

Collections Updated

Direct Updates

  • Projects Pulses - metadata.dashclicks_team.manager object

Update Pattern

{
$set: {
'metadata.dashclicks_team.manager': {
id: ObjectId,
name: String,
email: String
}
}
}

Use Cases

Use Case 1: Manager Reassignment

Scenario: Account Manager Alice leaves company, accounts reassigned to Bob

Flow:

  1. Admin updates 50 accounts: manager: Alice._idmanager: Bob._id
  2. Trigger fires 50 times (once per account)
  3. For each account:
    • Bob's user details queried (cached by MongoDB)
    • All 6 pulse types for that account updated
    • Manager field now shows Bob's name and email
  4. Bob sees all assigned accounts' pulses in dashboard
  5. Notifications route to Bob instead of Alice

Use Case 2: New Account Manager Assignment

Scenario: New client onboarded without assigned manager, later assigned

Flow:

  1. Account created with manager: null
  2. Pulses created for quarterly check-in and onboarding
  3. Manager assigned: manager: Carol._id
  4. Trigger fires immediately
  5. Carol's details added to pulse metadata
  6. Carol can now see and action these pulses

Use Case 3: Manager Change During Active Engagement

Scenario: Mid-cycle manager change for at-risk account

Flow:

  1. Account has active churn risk pulse assigned to Dave
  2. Account manager changed to Emma
  3. Trigger updates churn risk pulse metadata
  4. Emma now sees pulse in her dashboard
  5. Dave no longer sees it (unless still assigned via other means)
  6. Seamless handoff without pulse recreation

Performance Considerations

Optimizations

  1. Specific Field Watch: Only fires on manager field changes
  2. Early Exit: Returns immediately if no manager ID
  3. Single User Query: Only queries one user per trigger
  4. Lean Query: User query uses .lean() for performance
  5. Selective Fields: Only fetches needed user fields (_id, name, email)

Update Volume

For an account with:

  • 2 churn risk pulses
  • 1 quarterly check-in pulse
  • 1 loyalty milestone pulse
  • 0 other pulse types

Total Updates: 4 pulses updated in single updateMany call

Worst-Case Scenario

Account with maximum pulses:

  • Multiple churn risk pulses (historical)
  • Multiple quarterly check-in pulses (scheduled)
  • Multiple loyalty milestones (tier changes)
  • Review requests
  • Onboarding pulses

Maximum Updates: ~10-20 pulses per account (rare)
Typical Updates: 2-5 pulses per account

Why These Pulse Types?

Included Types

These 6 types represent customer success and retention activities where the account manager is the primary responsible party:

  • Churn Risk: Manager handles retention
  • Onboarding: Manager conducts kickoff
  • Quarterly Check-in: Manager relationship building
  • Loyalty Milestones: Manager recognition
  • Review Requests: Manager relationship leverage
  • New Inquiries: Manager first contact

Excluded Types

Other pulse types not updated:

  • inactive_task_pulse: Task-specific, not manager-driven
  • Internal pulses: Technical/operational, not customer-facing

Error Handling

User Not Found

If manager ID doesn't match a user:

if (manager) {
// Update pulses
} else {
// manager is null/undefined - no update performed
// No error logged (silent failure)
}

Update Errors

try {
const manager = await User.findById(managerId)...
await ProjectPulse.updateMany(...)
} catch (error) {
logger.error({
initiator: 'queue-manager/triggers/accounts',
error,
message: 'Error updating ProjectPulse metadata'
});
}

Upstream Triggers

  • users.js: If manager user's name/email changes, this trigger doesn't fire (user trigger handles order metadata)

Downstream Effects

  • Updated pulses may trigger:
    • Dashboard refresh
    • Notification routing changes
    • Manager workload recalculations

Complementary Systems

  • order.js trigger: Handles order-level manager changes
  • users.js trigger: Handles user profile updates

Metadata Structure

Pulse Metadata Format

Before manager change:

{
metadata: {
dashclicks_team: {
manager: {
id: ObjectId("oldManagerId"),
name: "Alice Smith",
email: "alice@dashclicks.com"
}
}
}
}

After manager change:

{
metadata: {
dashclicks_team: {
manager: {
id: ObjectId("newManagerId"),
name: "Bob Johnson",
email: "bob@dashclicks.com"
}
}
}
}

Monitoring

Key Metrics

  • Trigger Frequency: Manager changes per hour/day
  • Update Volume: Average pulses updated per manager change
  • Error Rate: Failed user queries or pulse updates
  • Manager Query Cache Hits: Same manager assigned to multiple accounts

Logging

// Success logging
logger.log({
initiator: 'queue-manager/triggers/accounts',
message: `Account ${accountId} manager changed to: ${managerId}`,
});

logger.log({
initiator: 'queue-manager/triggers/accounts',
message: `Updated ProjectPulse metadata for account ${accountId} with new manager: ${managerName}`,
});

// Error logging
logger.error({
initiator: 'queue-manager/triggers/accounts',
error,
message: 'Error updating ProjectPulse metadata',
});

Best Practices

1. Specific Field Watching

{ 'updateDescription.updatedFields.manager': { $exists: true } }

Only fires when exactly the field we care about changes.

2. Early Exit for Invalid Data

if (!managerId) return;

Prevents unnecessary processing and errors.

3. Null-Safe Update

if (manager) {
await ProjectPulse.updateMany(...)
}

Only updates if manager successfully retrieved.

4. Comprehensive Logging

Both success and error cases logged for observability.

Improvement Opportunities

1. Batch Processing for Mass Reassignments

For bulk manager changes (e.g., 100 accounts), could optimize:

// Current: 100 individual user queries
// Potential: Single query with caching
const managerCache = new Map();

2. Differential Updates

Only update pulses if manager actually changed:

const currentManager = pulse.metadata?.dashclicks_team?.manager?.id;
if (currentManager?.toString() !== managerId.toString()) {
// Update only if different
}

3. Update Other Pulse Types

Consider expanding to other pulse types where manager context matters.

Summary

The accounts trigger provides automatic synchronization of account manager changes to customer success pulses. By monitoring a single field (manager) and updating 6 pulse types, it ensures that manager reassignments are immediately reflected in all customer engagement tracking, enabling seamless team transitions without manual updates.

Key Characteristics:

  • 🎯 Highly focused - only watches manager field
  • 📊 Updates 6 customer success pulse types
  • ⚡ Immediate synchronization on manager change
  • 👥 Enables seamless manager transitions
  • 🔍 Simple, predictable behavior
  • 📝 Comprehensive logging for observability
💬

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