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.managerobject
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:
- Admin updates 50 accounts:
manager: Alice._id→manager: Bob._id - Trigger fires 50 times (once per account)
- 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
- Bob sees all assigned accounts' pulses in dashboard
- Notifications route to Bob instead of Alice
Use Case 2: New Account Manager Assignment
Scenario: New client onboarded without assigned manager, later assigned
Flow:
- Account created with
manager: null - Pulses created for quarterly check-in and onboarding
- Manager assigned:
manager: Carol._id - Trigger fires immediately
- Carol's details added to pulse metadata
- 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:
- Account has active churn risk pulse assigned to Dave
- Account manager changed to Emma
- Trigger updates churn risk pulse metadata
- Emma now sees pulse in her dashboard
- Dave no longer sees it (unless still assigned via other means)
- Seamless handoff without pulse recreation
Performance Considerations
Optimizations
- Specific Field Watch: Only fires on
managerfield changes - Early Exit: Returns immediately if no manager ID
- Single User Query: Only queries one user per trigger
- Lean Query: User query uses
.lean()for performance - 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'
});
}
Related Triggers
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
managerfield - 📊 Updates 6 customer success pulse types
- ⚡ Immediate synchronization on manager change
- 👥 Enables seamless manager transitions
- 🔍 Simple, predictable behavior
- 📝 Comprehensive logging for observability