📬 Notifications Service
📖 Overview
The Notifications Service is the centralized notification delivery system for the entire DashClicks platform. It handles all outbound communications across multiple channels:
- Email Notifications via SendGrid
- SMS Notifications via Twilio
- Firebase Cloud Messaging (FCM) for web push notifications
- Expo Push Notifications for mobile apps
Architecture: MongoDB Change Streams + Bull Queue + Scheduled Cron Jobs
Port: 5008 (HTTP endpoint for manual triggers)
Technology Stack: Node.js, Bull, Redis, MongoDB, SendGrid, Twilio, Firebase, Expo
🏗️ Architecture
Core Components
graph TB
subgraph "Trigger Layer"
CS[Change Streams<br/>MongoDB Watch]
CRON[Cron Jobs<br/>Scheduled Tasks]
HTTP[HTTP Endpoints<br/>Manual Triggers]
end
subgraph "Processing Layer"
SVC[Service Modules<br/>19+ Modules]
LOGIC[Business Logic<br/>Filtering & Validation]
end
subgraph "Queue Layer"
NQ[NotificationQueue<br/>MongoDB Collection]
BULL[Bull Queue<br/>Redis-backed]
end
subgraph "Delivery Layer"
SG[SendGrid<br/>Email]
TW[Twilio<br/>SMS]
FCM_D[Firebase<br/>Web Push]
EXPO[Expo<br/>Mobile Push]
end
CS --> SVC
CRON --> SVC
HTTP --> SVC
SVC --> LOGIC
LOGIC --> NQ
NQ --> BULL
BULL --> SG
BULL --> TW
BULL --> FCM_D
BULL --> EXPO
Notification Flow Pattern
3-Stage Process:
-
Trigger Detection:
- Change Streams detect MongoDB document changes in real-time
- Cron jobs run on schedule (every 5-30 seconds for different modules)
- HTTP endpoints receive manual triggers from other services
-
Queue Creation:
- Service modules validate and prepare notification data
- Insert notification records into
NotificationQueuecollection - Queue processor polls collection every 5 seconds
-
Delivery Execution:
- Bull queue picks up pending notifications
- Validates recipients (DND checks, token validation, credit checks)
- Calls external APIs (SendGrid, Twilio, FCM, Expo)
- Tracks delivery status and creates communication logs
🔧 Environment-Controlled Architecture
The service uses 30+ environment flags to enable/disable modules independently, allowing fine-grained control over which notification types are active:
# Core Queue Processor (REQUIRED - processes all queued notifications)
QUEUE_ENABLED=true
# Store Notifications
STORE_ENABLED=true # New subscriptions, renewals, cancellations
ORDERS_REMINDER_ENABLED=true # Order setup reminders
# CRM Notifications
QUEUE_CRM_REMINDERS_ENABLED=true # CRM task reminders
CONTACTS_IMPORT_ENABLED=true # Contact import completion
CONTACTS_CHANGE_ASSINGEE_ENABLED=true # Contact reassignment
DEALS_CHANGE_ASSINGEE_ENABLED=true # Deal reassignment
# Product Notifications
INSTASITES_VIEWED_ENABLED=true # InstaSite view tracking
INSTASITES_FAILED_ENABLED=true # InstaSite failure alerts
INSTAREPORTS_FAILED_ENABLED=true # InstaReport failure alerts
# Communication Notifications
FORMS_ENABLED=true # Form submissions
FORMS_NO_RESPONSE_ENABLED=true # 7-day no-response follow-ups
INBOUND_ENABLED=true # Inbound lead tracking
INBOUND_CAMPAIGN_DISCONNECTED_ENABLED=true # Campaign disconnections
INBOUND_LEAD_ASSIGNED_ENABLED=true # Lead assignments
SUPPORT_DELAYED_EMAILS_ENABLED=true # Support email delays
SUPPORT_NOTIFICATION_STREAM_ENABLED=true # Support ticket notifications
# Marketing Notifications
FUNNEL_ENABLED=true # Funnel-related notifications
AUTO_REVIEW_REQUEST_ENABLED=true # Automatic review requests
# Account Notifications
USERS_ACCEPT_INVITE=true # User invite acceptance
ACCOUNT_SIGNUP_REMINDER=true # Incomplete signup reminders
AFFILIATE_PAYOUT_ENABLED=true # Affiliate payout notifications
# Project Notifications
PROJECT_ENABLED=true # Project tasks, approvals, completions
# HTTP Endpoints
HOOKS_ENABLED=true # Enable HTTP endpoints
PORT=5008 # HTTP server port
# External Service Configuration
REDIS_HOST=localhost # Redis for Bull queue
REDIS_PORT=6379
SENDGRID_API_KEY=your_key # SendGrid API key
TWILIO_ACCOUNT_SID=your_sid # Twilio credentials
TWILIO_AUTH_TOKEN=your_token
FIREBASE_PROJECT_ID=your_project # Firebase FCM
Pattern: Only enabled modules load on startup. This allows:
- Running subset of modules for development/testing
- Disabling specific notification types without code changes
- Resource optimization by loading only necessary modules
- Independent scaling of different notification types
📦 Service Modules
The Notifications Service is organized into 19+ specialized modules, each handling a specific domain of notifications:
Core Services
Queue Processor (services/queue/)
Purpose: Bull queue processor - core delivery engine for all notifications
Trigger: Cron (every 5 seconds)
Environment: QUEUE_ENABLED=true
Flow: Poll NotificationQueue → Add to Bull → Deliver via external APIs
Supported Types: email, sms, fcm, expo-push
Critical: This module MUST be enabled for any notifications to be delivered
Store & Commerce Services
Store Notifications (services/store/)
Purpose: E-commerce notifications for subscriptions, invoices, and orders
Triggers:
- Change Stream on
StoreInvoice(past due invoices, renewals) - Change Stream on
StoreSubscription(new, canceled, upgraded/downgraded) - Cron jobs for onboarding reminders and order setup status
Environment:STORE_ENABLED=true,ORDERS_REMINDER_ENABLED=true
Events: - Past due invoices (
billing_reason: subscription_cycle,status: open) - Subscription renewals (
status: paid) - New subscriptions (
insert,status: active) - Cancellation requests (
cancel_at_period_end: true) - Subscription upgrades/downgrades (
previousPlan.idexists) - Order setup reminders (14-day follow-ups)
CRM Services
Contact Notifications (services/contacts/)
Purpose: Contact management notifications
Triggers:
- Change Stream on contact assignee changes
- Change Stream on contact imports
Environment: CONTACTS_CHANGE_ASSINGEE_ENABLED=trueCONTACTS_IMPORT_ENABLED=true
Notifications:- Contact reassignment alerts to new assignee
- Contact import completion summaries
Deal Notifications (services/deals/)
Purpose: Deal pipeline notifications
Trigger: Change Stream on deal assignee changes
Environment: DEALS_CHANGE_ASSINGEE_ENABLED=true
Notifications: Deal reassignment alerts
CRM Reminders (services/reminders/)
Purpose: Scheduled CRM task reminders
Trigger: Cron job (checks reminder schedules)
Environment: QUEUE_CRM_REMINDERS_ENABLED=true
Notifications: Task due date reminders, follow-up reminders
Product Services
InstaSites Notifications (services/instasites/)
Purpose: InstaSites product notifications
Triggers:
- Change Stream on
instasites.viewedfield updates - Change Stream on
instasites.status: failed
Environment: INSTASITES_VIEWED_ENABLED=trueINSTASITES_FAILED_ENABLED=true
Notifications:- View tracking notifications
- Failure alerts to account owners
InstaReports Notifications (services/instareports/)
Purpose: InstaReports failure alerts
Trigger: Change Stream on instareports.status: failed
Environment: INSTAREPORTS_FAILED_ENABLED=true
Notifications: Failure alerts with error details
Communication Services
Forms Notifications (services/forms/)
Purpose: Form submission tracking and follow-ups
Triggers:
- Change Stream on form submissions
- Cron job for 7-day no-response follow-ups
Environment: FORMS_ENABLED=trueFORMS_NO_RESPONSE_ENABLED=true
Notifications:- New form submission alerts
- 7-day no-response reminders
Inbound Leads (services/inbound/)
Purpose: Inbound lead tracking and campaign monitoring
Triggers:
- Change Stream on new inbound leads
- Change Stream on campaign disconnections
- Change Stream on lead assignments
Environment: INBOUND_ENABLED=trueINBOUND_CAMPAIGN_DISCONNECTED_ENABLED=trueINBOUND_LEAD_ASSIGNED_ENABLED=true
Notifications:- New lead alerts
- Campaign disconnection warnings
- Lead assignment notifications
Support Conversations (services/conversations/)
Purpose: Support ticket and conversation notifications
Triggers:
- Change Stream on support conversations
- Cron job for delayed email sending (batches messages)
Environment: SUPPORT_NOTIFICATION_STREAM_ENABLED=trueSUPPORT_DELAYED_EMAILS_ENABLED=true
Notifications:- New support message alerts
- Batched email summaries (reduces email frequency)
Marketing Services
Funnels (services/funnels/)
Purpose: Marketing funnel notifications
Trigger: Change Stream on funnel events
Environment: FUNNEL_ENABLED=true
Notifications: Funnel-related event notifications
Review Requests (services/reviews/)
Purpose: Automated review request system
Trigger: Cron job (every 30 seconds, checks review schedules)
Environment: AUTO_REVIEW_REQUEST_ENABLED=true
Features:
- Configurable delay before sending (e.g., 1 day after purchase)
- Optional reminders (repeating notifications)
- Send-once or repeat options (configurable retry count)
- Custom templates for email/SMS
- Integration with Stripe, Square, Shopify, QuickBooks
Notifications: - Initial review requests
- Reminder emails/SMS for non-responders
Account & User Services
User Invites (services/users/)
Purpose: User invitation notifications
Trigger: Change Stream on user invite acceptance
Environment: USERS_ACCEPT_INVITE=true
Notifications: Welcome emails on invite acceptance
Account Signup Reminders (services/account/)
Purpose: Incomplete signup follow-ups
Trigger: Cron job checking signup completion
Environment: ACCOUNT_SIGNUP_REMINDER=true
Notifications: Reminders to complete account setup
Affiliate Payouts (services/affiliates/)
Purpose: Affiliate program notifications
Trigger: Change Stream on affiliate payout records
Environment: AFFILIATE_PAYOUT_ENABLED=true
Notifications: Payout confirmation emails
Project Management Services
Project Notifications (services/projects/)
Purpose: Project management notifications
Triggers:
- Change Stream on project events
- Cron jobs for auto-approval, auto-completion, approval reminders
Environment:PROJECT_ENABLED=true
Notifications: - Task assignment alerts
- Approval request notifications
- Task completion confirmations
- Auto-approval reminders
Utility Services
A2P Compliance (services/a2p/)
Purpose: A2P (Application-to-Person) SMS compliance tracking
Details: Handles SMS compliance requirements
Communication Tracking (services/communication/)
Purpose: Communication log management
Details: Tracks all sent communications for audit and reporting
Domain Management (services/domains/)
Purpose: Domain-related notifications
Details: Lightning domain notifications
🔄 Queue Processing System
Queue Flow
sequenceDiagram
participant SM as Service Module
participant NQ as NotificationQueue<br/>(MongoDB)
participant CRON as Queue Processor<br/>(Cron: every 5s)
participant BULL as Bull Queue<br/>(Redis)
participant API as External API<br/>(SendGrid/Twilio/etc)
SM->>NQ: Insert notification record
Note over NQ: Document stored with:<br/>type, recipient, content, etc.
CRON->>NQ: Poll for pending notifications
NQ-->>CRON: Return pending items
CRON->>BULL: Add job(s) to Bull queue
Note over BULL: Redis-backed queue<br/>with retry logic
BULL->>BULL: Process job
BULL->>API: Send notification
API-->>BULL: Response
alt Success
BULL->>NQ: Delete from queue
BULL->>Communication: Save communication log
else Failure
BULL->>NQ: Increment tries counter
BULL->>BULL: Retry with exponential backoff
end
NotificationQueue Schema
The central queue collection that stores all pending notifications:
{
_id: ObjectId, // Used as Bull job ID
type: String, // 'email', 'sms', 'fcm', 'expo-push'
origin: String, // Module that created notification
// (e.g., 'reviews', 'store', 'contacts')
sender_account: ObjectId, // Sending account
sender_user: ObjectId, // Sending user (optional)
recipient: {
name: String, // Full name
first_name: String,
last_name: String,
email: String, // For email notifications
phone: String, // For SMS notifications
user_id: ObjectId // For FCM/Expo notifications
},
content: {
// For email:
template_id: String, // SendGrid template ID
subject: String,
body: String, // HTML content
additional_data: Object, // Template variables
additional_recipient_data: Object,
additional_message_data: {
from: String, // Custom from email
reply_to: Object, // Reply-to address
cc: Object, // CC recipients
bcc: Object, // BCC recipients
files: Array, // Attachments
in_reply_to: String, // For email threads
message_id: ObjectId, // Reference to conversation message
review_request_id: ObjectId
},
// For SMS:
content: String, // SMS text
from: String, // Twilio number
// For FCM:
title: String,
body: String,
click_action: String, // URL to open on click
data: Object, // Custom data payload
module: String,
type: String,
// For Expo:
// Similar to FCM
},
check_credits: Boolean, // Whether to check/deduct account credits
internal_sender: Boolean, // DashClicks internal notification (no credits)
tries: Number, // Retry attempt counter
last_error: String, // Last error message
created_at: Date,
updated_at: Date
}
Origin Values: contacts, deals, templates, conversations, instasites, inbound, instareports, dashclicks, analytics, projects, forms, reviews, users, sites, communication, reputation, lightning_domains, a2p_renewal, store, accounts, funnels, general
Queue Processor Configuration
The queue processor manages job execution with retry logic:
// From services/queue/queue.js
const MAX_RETRIES = 10;
const BACKOFF_DELAY = 10000; // 10 seconds
// Poll interval
cron.schedule('*/5 * * * * *', async () => {
// Every 5 seconds
await processQueuedList();
});
// Job configuration
{
jobId: notification._id.toString(),
attempts: MAX_RETRIES,
backoff: {
type: 'exponential',
delay: BACKOFF_DELAY
},
delay: 0 // Or custom delay for scheduled notifications
}
// Special handling for review requests
// Supports repeatable notifications with custom intervals
{
jobId: notification._id.toString(),
attempts: MAX_RETRIES,
backoff: { type: 'exponential', delay: BACKOFF_DELAY },
repeat: {
every: reminder * 24 * 60 * 60 * 1000, // days in milliseconds
limit: retry || 1 // Number of repeats
}
}
Job Processing Logic
The queue processor handles four notification types:
Email Processing:
- Create sender payload from account business info
- Generate SendGrid payload with template/content
- Check DND (Do Not Disturb) list
- Send via SendGrid API
- Save communication log
- Delete from queue on success
SMS Processing:
- Fetch Twilio account credentials
- Generate Twilio payload
- Check DND list
- Send via Twilio API
- Save communication log
- Delete from queue on success
FCM Processing:
- Build notification with title, body, click action
- Send to Firebase Cloud Messaging
- Delete from queue on success
Expo Push Processing:
- Fetch user's Expo push tokens
- Generate Expo payload
- Validate tokens (remove invalid)
- Send via Expo Push API
- Delete from queue on success
📧 Delivery Mechanisms
Email (SendGrid)
Flow: NotificationQueue → Bull → SendGrid API → Communication Log
Key Components:
utils/senderCreate.js::generateSendgridPayload()- Payload generationutilities2/src/notification.js::sendEmail()- API callutils/communications.js::saveEmailCommunication()- Logging
Features:
- Template-based emails (SendGrid dynamic templates)
- Dynamic data injection (sender info, recipient info, custom variables)
- DND (Do Not Disturb) list checking
- Credit usage tracking (can use account's SendGrid or DashClicks')
- Communication history logging
- Reply-to, CC, BCC support
- File attachments
- Email threading (in-reply-to headers)
Payload Structure:
{
from: {
name: "User from Business Name", // Or just business name
email: "info@dashboardnotifications.com" // Or custom
},
personalizations: [{
to: [{ name: "Recipient Name", email: "recipient@example.com" }],
cc: [{ email: "cc@example.com" }], // Optional
bcc: [{ email: "bcc@example.com" }], // Optional
dynamic_template_data: {
sender: {
name: "Owner Name",
first_name: "John",
last_name: "Doe",
email: "business@example.com",
account: "Business Name",
address: { street, unit, city, state, zip, country },
website: "https://example.com",
logo: "https://cdn.example.com/logo.png",
phone: "(123) 456-7890",
social: { facebook, linkedin, youtube, twitter, instagram }
},
recipient: {
name: "Recipient Name",
first_name: "Jane",
last_name: "Smith",
email: "recipient@example.com"
},
// Module-specific variables
...additionalData
}
}],
template_id: "d-1234567890abcdef", // SendGrid template
reply_to: { email: "reply@example.com" }, // Optional
headers: {
"In-Reply-To": "<message-id>", // For threading
"References": "<message-id>"
},
attachments: [{ // Optional
filename: "document.pdf",
content: "base64_encoded_content",
type: "application/pdf"
}]
}
SendGrid API Key Management:
- Each account can have its own SendGrid API key
- Falls back to DashClicks master key if account key invalid
- Automatically regenerates invalid keys using master key
- Supports SendGrid subusers for account isolation
SMS (Twilio)
Flow: NotificationQueue → Bull → Twilio API → Communication Log
Key Components:
utils/senderCreate.js::generateTwilioPayload()- Payload generationutilities2/src/notification.js::sendSMS()- API callutils/communications.js::saveSMSCommunication()- Logging
Features:
- DND list checking
- Credit usage tracking
- A2P (Application-to-Person) compliance
- Communication history logging
- Status callbacks for delivery tracking
- Messaging service support (for number pools)
Payload Structure:
{
accountSID: "AC1234567890abcdef", // Twilio account SID
authToken: "your_auth_token",
to: "+12345678901", // Recipient phone (E.164 format)
// Option 1: Specific number
from: "+19876543210",
// Option 2: Messaging service (for DashClicks internal)
messagingServiceSid: "MG1234567890abcdef",
body: "Your SMS message content",
statusCallback: "https://api.dashclicks.com/webhook?status=true&tracking=JWT_TOKEN"
}
Credit Handling:
- If
check_credits: true→ Uses account's Twilio credentials - If
check_credits: false→ Uses DashClicks master Twilio account - Status callbacks include JWT token for tracking
FCM (Firebase Cloud Messaging)
Flow: NotificationQueue → Bull → FCM API
Key Components:
utilities2/src/notification.js::sendFCM()- API call
Features:
- Web push notifications (browser)
- Bell notifications (in-app notification center)
- Account and user targeting
- Custom click actions
- Data payloads for custom handling
- Token management and validation
- Socket emission for real-time bell notifications
Payload Structure:
{
message: {
token: "fcm_device_token", // Or array of tokens
webpush: {
headers: {
Urgency: "high",
TTL: "300" // Time to live in seconds
},
notification: {
title: "Notification Title",
body: "Notification body text",
icon: "https://cdn.example.com/icon.png",
click_action: "https://app.dashclicks.com/path"
},
data: { // All values must be strings
module: "contacts",
type: "assignment",
account_id: "507f1f77bcf86cd799439011",
// Additional custom data
},
fcm_options: {
link: "https://app.dashclicks.com/path"
}
}
}
}
Notification Types:
- Browser Push: Shows OS-level notification (requires
subType: 'browser') - Bell Notification: Stored in database for in-app notification center (requires
subType: 'bell')
Bell Notification Flow:
- Save to
FcmNotificationcollection - Emit via socket to
bell_notificationevent - Real-time delivery to connected users
- Persisted for retrieval when user comes online
Expo Push (Mobile)
Flow: NotificationQueue → Bull → Expo Push API
Key Components:
utils/senderCreate.js::generateExpoPayload()- Payload generationutilities2/src/notification.js::sendExpoPushNotification()- API call
Features:
- Mobile app push notifications (iOS & Android)
- Token validation (checks Expo.isExpoPushToken())
- Batch delivery (chunks notifications for API limits)
- Automatic token cleanup (removes invalid tokens)
Payload Structure:
[
{
to: 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]',
title: 'Notification Title',
body: 'Notification body text',
sound: 'default',
data: {
// Custom data payload
module: 'projects',
type: 'task_assigned',
project_id: '507f1f77bcf86cd799439011',
},
},
// ... more notifications
];
Token Management:
- Tokens stored in
ExpoPushTokencollection - Each user can have multiple tokens (multiple devices)
- Invalid tokens are logged and can be cleaned up
- Supports chunked delivery for large batches
🚨 Error Handling & Retry Logic
Retry Strategy
The queue processor implements exponential backoff for failed deliveries:
attempts: 10 // Maximum retry attempts
backoff: {
type: 'exponential',
delay: 10000 // Base delay: 10 seconds
}
// Retry delays:
Attempt 1: 10 seconds
Attempt 2: 20 seconds
Attempt 3: 40 seconds
Attempt 4: 80 seconds
Attempt 5: 160 seconds (~2.7 minutes)
Attempt 6: 320 seconds (~5.3 minutes)
Attempt 7: 640 seconds (~10.7 minutes)
Attempt 8: 1280 seconds (~21.3 minutes)
Attempt 9: 2560 seconds (~42.7 minutes)
Attempt 10: 5120 seconds (~85.3 minutes)
Total time: ~2.7 hours before final failure
Failure Tracking
On each failed attempt:
- Increment try counter:
triesfield inNotificationQueueincremented - Store error message:
last_errorfield updated with error details - Log error: Error logged with full context (job ID, data, error)
- Retry with backoff: Job automatically retried by Bull
After MAX_RETRIES (10 attempts):
- Create failed communication log: Save record of failure for audit
- Delete from queue: Remove notification to prevent further attempts
- Final error log: Log final failure with all attempt details
DND (Do Not Disturb) Checks
Before sending email or SMS, the system checks the DND list:
const dnd = await DND.find({
account_id: sender_account,
value: { $in: [recipient_email_or_phone] },
});
if (dnd.length) {
// Delete from queue, don't send
// For repeatable jobs (review reminders), only skip this attempt
await QueueModel.deleteOne({ _id: job.id });
return done();
}
DND List Sources:
- User-initiated unsubscribes
- Bounce/complaint handling
- Admin-added blocks
- Regulatory compliance (e.g., TCPA for SMS)
Communication Logging
All attempts (success or failure) are logged in the Communication collection:
Successful Communication Log:
{
user_id: ObjectId, // Sender user
account_id: ObjectId, // Sender account
module: String, // Origin module (e.g., 'reviews')
// Email-specific
to: String, // Recipient email
from: String, // Sender email
subject: String,
body: String, // HTML content
sendgrid_message_id: String,
// SMS-specific
to_number: String, // Recipient phone
from_number: String, // Twilio number
message: String, // SMS text
twilio_message_id: String,
// Tracking
message_id: ObjectId, // Related conversation message
review_request_id: ObjectId,
contact_id: ObjectId, // Related contact
// Status
success: true,
use_credits: Boolean, // Whether credits were deducted
created_at: Date
}
Failed Communication Log (after max retries):
{
// Same fields as above, plus:
success: false,
error: String, // Final error message
tries: 10 // Number of attempts
}
User Preference Verification
Before creating notifications, modules can check user preferences:
const canNotify = await NotificationUtil.verify({
userID: user_id,
accountID: account_id,
module: 'reviews', // Module name
type: 'requests', // Notification type
subType: 'email', // Channel (email/sms/fcm/etc)
});
if (!canNotify) {
// User opted out, skip notification
return;
}
Preference Hierarchy:
- Check if user has required scope/permissions for module
- Check user's notification preferences in
UserConfig - Default to enabled if no preference set (opt-out model)
- Respect explicit
falsesettings
🔗 Integration with Other Services
Internal API
Relationship: Creates notification triggers and manages user preferences
Integration Points:
- Review Requests: Internal API creates
ReviewRequestdocuments → Notification Service sends emails/SMS - Deal Assignments: Internal API updates deal assignee → Change Stream triggers notification
- Contact Imports: Internal API completes import → Change Stream triggers summary email
- User Preferences: Internal API manages
UserConfigfor notification opt-out
Data Flow:
Internal API → MongoDB → Change Stream → Notification Service
Internal API → Create ReviewRequest → Cron Job → Notification Service
Queue Manager
Relationship: Creates scheduled notifications for background jobs
Integration Points:
- InstaReports: Queue Manager processes reports → Updates status → Change Stream triggers notification
- InstaSites: Queue Manager builds sites → Updates status → Change Stream triggers notification
- Analytics: Queue Manager processes data → May trigger summary notifications
Data Flow:
Queue Manager → MongoDB Status Update → Change Stream → Notification Service
Conversation Socket
Relationship: Real-time chat triggers notification batching
Integration Points:
- Support Messages: New support messages → Notification Service batches for email summaries
- Message Tracking: Conversation Socket tracks unread → Notification Service sends reminders
Data Flow:
Conversation Socket → Support Message → Change Stream → Notification Service
Notification Service → Delayed Email Batch → SendGrid → Email Delivery
General Socket
Relationship: Real-time event distribution for bell notifications
Integration Points:
- Bell Notifications: Notification Service → Socket emission → Connected clients
- Real-time Updates: FCM bell notifications delivered via socket
Data Flow:
Notification Service → Save Bell Notification → Emit Socket Event → General Socket → Clients
External API
Relationship: Third-party integrations trigger notifications
Integration Points:
- Stripe Webhooks: Subscription events → Store notifications
- Twilio Status Callbacks: SMS delivery status → Update communication logs
- SendGrid Webhooks: Email events (opens, clicks, bounces) → Update logs
📊 Key Models
NotificationQueue
Collection: notifications.queue
Purpose: Pending notification storage
Used By: All service modules (write), queue processor (read/delete)
Indexes:
type- For filtering by notification typetries- For retry logicorigin- For tracking source module
Document Lifecycle:
- Created by service module
- Polled by queue processor (every 5 seconds)
- Added to Bull queue
- Deleted on successful delivery
- Updated with
triesandlast_erroron failure - Deleted after MAX_RETRIES
DND (Do Not Disturb)
Collection: dnds
Purpose: Block list for emails/phones
Used By: Queue processor (email/SMS validation)
Fields:
{
account_id: ObjectId, // Account that owns this DND entry
value: String, // Email or phone number
type: String, // 'email' or 'phone'
reason: String, // 'user_request', 'bounce', 'complaint', etc.
created_at: Date
}
Sources:
- User unsubscribe requests
- Bounce handling (hard bounces, spam complaints)
- Admin-added blocks
- Regulatory compliance
Communication
Collection: communications
Purpose: Notification delivery logs (audit trail)
Used By: Queue processor (logging), reporting
Retention: Permanent (for compliance and reporting)
Uses:
- Audit trail for all sent communications
- Credit usage tracking
- Delivery status monitoring
- Contact last_contacted updates
- Compliance reporting
Config
Collection: configs
Purpose: Active notification module configuration
Document:
{
_id: ObjectId,
type: "notification-modules",
active: ["email", "sms", "fcm", "expo-push"]
}
Used By: Queue processor (filtering notification types)
Purpose: Allows enabling/disabling notification channels without code changes
ExpoPushToken
Collection: expo_push_tokens
Purpose: User device tokens for Expo push notifications
Used By: Expo notification payload generation
Fields:
{
user_id: ObjectId,
account_id: ObjectId,
token: String, // ExponentPushToken[...]
device_info: Object, // Optional device metadata
created_at: Date
}
FcmToken
Collection: fcm_tokens
Purpose: User device tokens for Firebase Cloud Messaging
Used By: FCM notification payload generation
Fields:
{
user_id: ObjectId,
account_id: ObjectId,
web_token: [String], // Array of FCM tokens for web
created_at: Date
}
FcmNotification
Collection: fcm_notifications
Purpose: Bell notification storage (in-app notification center)
Used By: FCM bell notification type
Fields:
{
module: String, // Origin module
type: String, // Notification type
users: [ObjectId], // Recipient users
account: ObjectId, // Account
message: {
title: String,
body: String,
data: Object,
click_action: String
},
metadata: Object, // Module-specific data
read_by: [ObjectId], // Users who read this
created_at: Date
}
UserConfig
Collection: user_configs
Purpose: User notification preferences
Used By: Notification verification (opt-out checking)
Fields:
{
user_id: ObjectId,
account_id: ObjectId,
type: String, // Module type
preferences: {
[module]: {
notifications: {
[type]: {
email: Boolean, // true/undefined = enabled, false = disabled
sms: Boolean,
browser: Boolean,
bell: Boolean
}
}
}
}
}
🎯 Common Patterns
Change Stream Pattern
All change stream-based modules follow this pattern:
// From services/store/changeStream.js
const stream = Model.watch(
[
{
$match: {
// Filter for specific operations and conditions
operationType: 'update',
'fullDocument.status': 'active',
'updateDescription.updatedFields.field': { $exists: true },
},
},
{
$addFields: {
// Add metadata flags for notification types
'metadata.notification_type': {
$cond: [
{
$and: [
/* conditions */
],
},
true,
false,
],
},
},
},
],
{
fullDocument: 'updateLookup', // Include full document
startAtOperationTime: RESUMETIME || undefined, // Resume from last run
},
);
stream.on('change', async data => {
await processNotifications(data);
});
Key Elements:
- $match stage: Filter relevant operations
- $addFields stage: Add metadata flags for notification routing
- fullDocument: 'updateLookup': Always include full document state
- startAtOperationTime: Resume from last timestamp (prevents duplicates on restart)
- Error handling: Automatic reconnection on disconnect
Cron Job Pattern
All cron-based modules follow this pattern:
// From index.js
let inProgress = false;
cron.schedule('*/30 * * * * *', async () => {
if (!inProgress) {
inProgress = true;
try {
await processNotifications();
} catch (err) {
logger.error({ error: err });
} finally {
inProgress = false;
}
}
});
Key Elements:
- inProgress flag: Prevents overlapping executions
- Try-catch: Ensures errors don't crash the service
- Finally block: Always resets inProgress flag
Queue Creation Pattern
All modules create notifications using this pattern:
// Service module creates notification
await NotificationQueue.create({
type: 'email', // or 'sms', 'fcm', 'expo-push'
origin: 'reviews', // Source module
sender_account: account_id,
sender_user: user_id, // Optional
recipient: {
name: contact.name,
first_name: contact.first_name,
last_name: contact.last_name,
email: contact.email, // For email
phone: contact.phone, // For SMS
user_id: user._id, // For FCM/Expo
},
content: {
template_id: 'd-1234567890abcdef', // For email
subject: 'Your Subject',
additional_data: {
// Template variables
},
additional_message_data: {
// Tracking data
message_id: conversation_message_id,
review_request_id: review_request_id,
},
},
check_credits: true, // Deduct credits
internal_sender: false, // Not DashClicks internal
});
Notification Helper Functions
Modules use helper functions for consistency:
// From utils/notification.js
await processEmailv2({
verification: {
module: 'reviews',
type: 'requests',
subType: 'email',
},
recipient: {
accountID: account_id,
users: [contact1, contact2], // Array of contacts
},
content: {
sender_user: user_id,
subject: 'Subject',
body: '<html>...</html>', // Or template_id
},
user_check: false, // Skip user preference check
check_credits: true,
additional_message_data: {
/* tracking */
},
});
await processSMSv2({
verification: { module, type, subType },
recipient: { accountID, users },
data: {
content: 'SMS text',
from: '+19876543210',
},
sender_user: user_id,
user_check: false,
check_credits: true,
additional_message_data: {
/* tracking */
},
});
📈 Statistics
Service Modules: 19+ specialized modules
Environment Flags: 30+ configuration options
Notification Types: 4 (email, SMS, FCM, Expo)
Max Retries: 10 attempts per notification
Queue Poll Interval: 5 seconds
Base Backoff Delay: 10 seconds
Total Retry Time: ~2.7 hours before final failure
Supported External APIs: SendGrid, Twilio, Firebase, Expo
Change Streams: 15+ active MongoDB watch streams
Cron Jobs: 20+ scheduled tasks
🐛 Troubleshooting
Issue: Notifications Not Being Sent
Symptoms: Users report not receiving emails/SMS
Diagnosis Steps:
-
Check environment flags:
# Ensure required flags are enabled
QUEUE_ENABLED=true # REQUIRED for all notifications
[MODULE]_ENABLED=true # Module-specific flag -
Check NotificationQueue:
// Check if notifications are being created
db.getCollection('notifications.queue').find({
sender_account: ObjectId('account_id'),
created_at: { $gte: new Date(Date.now() - 3600000) }, // Last hour
}); -
Check service logs:
# Look for "RUNNING" messages
grep "SERVICE RUNNING" notifications.log
# Look for errors
grep "error" notifications.log -
Check Redis connection:
redis-cli -h $REDIS_HOST -p $REDIS_PORT ping
# Should return: PONG -
Check external API credentials:
// For SendGrid
const key = await SendgridKey.findOne({ account_id });
// For Twilio
const account = await Account.findById(account_id, { twilio_account: 1 });
Issue: Notifications Delayed
Symptoms: Notifications arrive late
Causes:
-
Queue backlog: Too many pending notifications
// Check queue size
db.getCollection('notifications.queue').count(); -
Retry delays: Notifications in exponential backoff
// Check retrying notifications
db.getCollection('notifications.queue').find({ tries: { $gte: 1 } }); -
External API rate limits: SendGrid/Twilio throttling
- Check API dashboard for rate limit errors
- Consider upgrading API plan
Solutions:
- Scale up Redis for Bull queue
- Add more notification service instances
- Increase queue processor poll frequency (reduce from 5s to 3s)
- Implement queue prioritization for critical notifications
Issue: Duplicate Notifications
Symptoms: Users receive same notification multiple times
Causes:
-
Repeatable job configuration: Review requests configured to repeat
// Check auto review request settings
db.getCollection('reviews.auto_requests').findOne({
account_id: ObjectId('account_id'),
}); -
Multiple service instances: Same change stream event processed twice
- Ensure
startAtOperationTimeis properly configured - Check for service restart loops
- Ensure
-
Failed deletion: Notification not deleted after success
- Check for transaction errors in logs
- Verify MongoDB connection stability
Solutions:
- Review repeat configuration for review requests
- Implement idempotency checks using message IDs
- Ensure proper transaction handling
- Add duplicate detection based on content/recipient/timestamp
Issue: Notifications Failing with Errors
Symptoms: High failure rate in logs
Common Errors:
SendGrid 401 Unauthorized:
// API key expired or invalid
// Solution: Regenerate key (automatically handled)
// Check: Account.sendgrid.subuser.username
Twilio 21211 Invalid 'To' Number:
// Phone number not in E.164 format
// Solution: Validate phone format before queuing
// Use: awesome-phonenumber library
FCM Invalid Token:
// Token expired or unregistered
// Solution: Remove invalid tokens from FcmToken collection
Expo DeviceNotRegistered:
// Token no longer valid
// Solution: Remove from ExpoPushToken collection
DND Check Blocking:
// Email/phone in DND list
// Solution: User must unsubscribe or admin remove
// Check: DND.find({ value: email_or_phone })
Issue: Credits Not Being Deducted
Symptoms: Notifications sent but credits unchanged
Check:
-
check_credits flag:
// Ensure flag is true in NotificationQueue
{
check_credits: true;
} -
internal_sender flag:
// Ensure internal_sender is false
{
internal_sender: false;
} -
Account balance:
// Check OneBalance credits
db.getCollection('onebalance').findOne({
account_id: ObjectId('account_id'),
});
📝 Best Practices
For Service Module Development
- Always use helper functions:
processEmailv2(),processSMSv2() - Include user verification: Check preferences before creating notifications
- Add comprehensive metadata: Include all tracking IDs for audit trail
- Handle errors gracefully: Catch and log errors, don't crash service
- Use transactions: Ensure atomic operations for critical updates
- Test DND scenarios: Verify DND list is respected
- Implement resume tokens: Use
startAtOperationTimefor change streams
For Notification Content
- Use templates: SendGrid dynamic templates for consistency
- Personalize: Include recipient name, sender info
- Mobile-friendly: Ensure emails render well on mobile
- Clear CTAs: Make action items obvious
- Unsubscribe links: Include in all marketing emails
- Test thoroughly: Send test notifications before production
For Operations
- Monitor queue size: Alert if queue grows beyond threshold
- Track delivery rates: Monitor success/failure ratios
- Review logs regularly: Check for recurring errors
- Update API keys: Rotate credentials periodically
- Scale proactively: Add instances before hitting limits
- Backup configuration: Document all environment flags
🔗 Related Documentation
- Queue Manager Documentation - Background job processing
- Internal API Communication Endpoints (link removed - file does not exist) - Conversation API
- Shared Models - MongoDB schema reference
- External Integrations: SendGrid (link removed - file does not exist) - Email delivery
- External Integrations: Twilio (link removed - file does not exist) - SMS delivery
📚 Additional Resources
External Documentation
- SendGrid API Reference
- Twilio API Reference
- Firebase Cloud Messaging
- Expo Push Notifications
- Bull Queue Documentation
Internal Resources
- Notification Templates (link removed - file does not exist)
- Credit System Documentation (link removed - file does not exist)
- User Preferences API (link removed - file does not exist)
Service Type: Background Processing + HTTP Endpoint
Port: 5008 (HTTP endpoints)
Dependencies: MongoDB, Redis, SendGrid, Twilio, Firebase, Expo
Status: Core Service - handles all platform notifications
Maintainer: Platform Team
Last Updated: October 2025