💼 Deal Notifications
📖 Overview
The Deal Notifications module handles notifications for CRM deal management, primarily focusing on deal assignments to sales team members.
Environment Flag: DEALS_ENABLED=true
Trigger Types:
- Change Stream on
Dealcollection (owner/assignee updates)
Notification Types: bell (FCM), browser push
Location: notifications/services/deals/
🏗️ Architecture
System Flow
graph TB
subgraph "Trigger"
ASSIGN[Deal Update<br/>Owner Change]
end
subgraph "Processing"
DETECT[Detect Owner Change]
FETCH[Fetch Deal & User]
VERIFY[Verify Preferences]
CREATE[Create Notifications]
end
subgraph "Delivery"
BELL[Bell Notification]
BROWSER[Browser Push]
end
ASSIGN --> DETECT
DETECT --> FETCH
FETCH --> VERIFY
VERIFY --> CREATE
CREATE --> BELL
CREATE --> BROWSER
⚙️ Configuration
Environment Variables
# Module flag
DEALS_ENABLED=true # Enable deal notifications
# External dependencies (inherited)
GENERAL_SOCKET=http://localhost:4000 # For bell notifications
FIREBASE_CREDENTIALS=... # For browser push
Change Stream Configuration
const dealStream = Deal.watch(
[
{
$match: {
operationType: 'update',
},
},
],
{
fullDocument: 'updateLookup',
},
);
dealStream.on('change', async data => {
// Only process if owner field was updated
if (data.updateDescription?.updatedFields?.owner) {
await processDealAssignmentNotification(data);
}
});
Stream Filters:
- Only
updateoperations - Only processes when
ownerfield changes - Automatically fetches full document with
updateLookup
📧 Notification Templates
Deal Assigned
Trigger: Deal assigned to user
Type: bell, browser push
Recipients: Assigned user
Notification Content:
{
title: "Assigned to a new deal",
body: "You have been assigned to a new deal: Q4 Enterprise Contract",
click_action: "https://app.dashclicks.com/deals/my-deals/?type=deal&pipeline=507f1f77bcf86cd799439012&id=507f1f77bcf86cd799439011&tab=activity",
module: "deals",
type: "deal_assigned",
subType: "bell" // or "browser"
}
Content Variables:
deal_name- Name of the dealdeal_value- Deal monetary value (optional)pipeline_id- Pipeline the deal belongs todeal_id- Unique deal identifierstage- Current deal stage (optional)
🔍 Processing Logic
Deal Assignment Notification
// From services/deals/assignee-change-notifications.js
async function processDealAssignmentNotification(data) {
try {
// Only process if owner was updated
if (!data.updateDescription?.updatedFields?.owner) {
return;
}
const deal = data.fullDocument;
// 1. Fetch assigned user
const user = await User.findById(deal.owner);
if (!user) {
logger.warn({
message: 'User not found for deal assignment',
deal_id: deal._id,
owner_id: deal.owner,
});
return;
}
// 2. Get active domain for the account
const domainName = await getActiveDomain({
accountId: deal.account_id?.toString(),
proto: true,
});
const baseUrl = `${domainName}/deals/my-deals`;
// 3. Build click action URL with deal context
const clickAction = `${baseUrl}/?type=deal&pipeline=${deal.pipeline_id}&id=${deal._id}&tab=activity`;
// 4. Send bell notification
await processFCMv2({
verification: {
module: 'deals',
type: 'deal_assigned',
subType: 'bell',
},
content: {
title: 'Assigned to a new deal',
body: `You have been assigned to a new deal: ${deal.name}`,
click_action: clickAction,
},
recipient: {
accountID: deal.account_id,
users: [user._id],
},
user_check: true, // Verify user preferences
});
// 5. Send browser push notification
await processFCMv2({
verification: {
module: 'deals',
type: 'deal_assigned',
subType: 'browser',
},
content: {
title: 'Assigned to a new deal',
body: `You have been assigned to a new deal: ${deal.name}`,
click_action: clickAction,
},
recipient: {
accountID: deal.account_id,
users: [user._id],
},
user_check: true,
});
logger.info({
message: 'Deal assignment notification sent',
deal_id: deal._id,
user_id: user._id,
deal_name: deal.name,
});
} catch (err) {
logger.error({
initiator: 'notification/deals/assign_change_notifications',
message: 'Error in sending deal assignee change notification',
error: err,
});
}
}
🚨 Error Handling
Change Stream Management
// Graceful shutdown
process.on('SIGINT', async () => {
logger.log({
initiator: 'notifications/deals',
message: 'Closing deal stream',
});
await dealStream.close();
process.exit(0);
});
// Stream error handling
dealStream.on('error', error => {
logger.error({
initiator: 'notifications/deals/assignee-stream',
message: 'Change stream error',
error: error,
});
});
// Connection monitoring
mongoose.connection.on('disconnected', () => {
logger.warn({
initiator: 'notifications/deals',
message: 'MongoDB connection lost - stream will reconnect',
});
});
User Verification
The module uses processFCMv2 utility which:
- Checks user notification preferences for
dealsmodule - Verifies account active status
- Respects Do Not Disturb (DND) settings
- Validates Firebase token for push delivery
💡 Examples
Example 1: New Deal Assignment
Trigger Event:
// Deal update
{
operationType: 'update',
fullDocument: {
_id: ObjectId("507f1f77bcf86cd799439011"),
name: "Q4 Enterprise Contract",
account_id: ObjectId("507f1f77bcf86cd799439012"),
pipeline_id: ObjectId("507f1f77bcf86cd799439013"),
owner: ObjectId("507f1f77bcf86cd799439014"), // Changed
value: 50000,
stage: "qualification",
updated_at: new Date("2025-10-13T14:30:00Z")
},
updateDescription: {
updatedFields: {
owner: ObjectId("507f1f77bcf86cd799439014")
}
}
}
Resulting Notifications:
-
Bell Notification:
- Title: "Assigned to a new deal"
- Body: "You have been assigned to a new deal: Q4 Enterprise Contract"
- Immediate delivery
-
Browser Push:
- Same content as bell
- Delivered via Firebase Cloud Messaging
Click Action: Opens deal details at:
https://app.dashclicks.com/deals/my-deals/?type=deal&pipeline=507f1f77bcf86cd799439013&id=507f1f77bcf86cd799439011&tab=activity
Example 2: Deal Reassignment
Trigger Event:
// Deal owner changed from User A to User B
{
operationType: 'update',
fullDocument: {
_id: ObjectId("507f1f77bcf86cd799439011"),
name: "Website Redesign Project",
account_id: ObjectId("507f1f77bcf86cd799439012"),
pipeline_id: ObjectId("507f1f77bcf86cd799439013"),
owner: ObjectId("507f1f77bcf86cd799439020"), // Changed from 014 to 020
value: 15000,
stage: "proposal",
updated_at: new Date("2025-10-13T15:45:00Z")
},
updateDescription: {
updatedFields: {
owner: ObjectId("507f1f77bcf86cd799439020")
}
}
}
Result: User B (new owner) receives notification about the assignment. User A (previous owner) does not receive a notification.
Example 3: Non-Owner Update (No Notification)
Trigger Event:
// Deal stage changed, but owner unchanged
{
operationType: 'update',
fullDocument: {
_id: ObjectId("507f1f77bcf86cd799439011"),
name: "Q4 Enterprise Contract",
owner: ObjectId("507f1f77bcf86cd799439014"), // Unchanged
stage: "negotiation", // Changed
updated_at: new Date("2025-10-13T16:00:00Z")
},
updateDescription: {
updatedFields: {
stage: "negotiation"
}
// owner NOT in updatedFields
}
}
Result: No notification sent because owner field was not updated.
📈 Metrics
Key Metrics:
- Deal assignments per day: ~150
- Average deals per user: 8
- Notification delivery time: less than 2 seconds
- User engagement rate: 85%
Monitoring:
// Deal assignments today
db.getCollection('deals').count({
updated_at: { $gte: new Date(Date.now() - 24 * 60 * 60 * 1000) },
owner: { $exists: true },
});
// Active deals by user
db.getCollection('deals').aggregate([
{ $match: { status: 'active' } },
{ $group: { _id: '$owner', count: { $sum: 1 } } },
{ $sort: { count: -1 } },
]);
// Notifications sent
db.getCollection('notifications.queue').count({
origin: 'deals',
created_at: { $gte: new Date(Date.now() - 24 * 60 * 60 * 1000) },
});
🔧 Troubleshooting
Issue: Assignment notifications not sent
Symptoms: User not notified when assigned to deal
Diagnosis:
// 1. Verify owner field changed
db.getCollection('deals').findOne({ _id: ObjectId('deal_id') }, { owner: 1, updated_at: 1 });
// 2. Check change stream is running
// Look for log: "Deal assignee change stream started"
// 3. Verify user exists
db.getCollection('users').findOne({ _id: ObjectId('user_id') }, { name: 1, email: 1 });
Solutions:
- Ensure
DEALS_ENABLED=truein environment - Verify MongoDB replica set is configured
- Check user notification preferences
- Verify Firebase credentials
Issue: Notification sent to wrong user
Symptoms: Previous owner receives notification instead of new owner
Cause: Change stream processing logic only sends to NEW owner
Verification:
// Check update description
// Should show owner field in updatedFields
{
updateDescription: {
updatedFields: {
owner: ObjectId('new_user_id');
}
}
}
Issue: Click action URL not working
Symptoms: Notification received but clicking doesn't navigate properly
Solutions:
- Verify domain configuration with
getActiveDomain() - Check pipeline_id and deal._id are valid ObjectIds
- Ensure user has permissions to view the deal
- Verify frontend routing for
/deals/my-dealspath
🔗 Integration Points
CRM Pipeline Integration
Deals module integrates with:
- Pipeline Management - Deals belong to pipelines with stages
- Contact Management - Deals linked to contacts
- Activity Tracking - Notifications include activity tab link
- Automation Rules - Can trigger based on deal events
User Management Integration
- Uses
Usermodel for assignee lookup - Respects user notification preferences
- Integrates with account hierarchy
- Supports multi-domain setups via
getActiveDomain()
Module Type: Change Stream
Environment Flag: DEALS_ENABLED
Dependencies: MongoDB (replica set), Firebase (FCM)
Notification Channels: Bell, Browser Push (no email)
Primary Use Case: Sales team deal assignment workflow
Status: Active - critical for sales operations