โฐ Support Snooze Processing
๐ Overviewโ
The Support Snooze job automatically unsnoozes support conversation rooms when their snooze time expires. It runs every 10 seconds to check for rooms that need to be unsnoozed and processes them to restore them to active support queue visibility.
Complete Flow:
- Cron Initialization:
queue-manager/crons/conversations/support.snooze.js - Service Processing:
queue-manager/services/conversations/support.snooze.js - Queue Definition:
queue-manager/queues/conversations/support.snooze.js
Execution Pattern: Cron-based (every 10 seconds)
Queue Name: support-snooze
Environment Flag: QM_CONVERSATIONS=true (in index.js)
๐ Complete Processing Flowโ
sequenceDiagram
participant CRON as Cron Schedule<br/>(every 10s)
participant SERVICE as Snooze Service
participant DB as SupportRoom<br/>Collection
participant QUEUE as Bull Queue
participant SOCKET as General Socket
CRON->>SERVICE: Check snooze expiry
SERVICE->>DB: Query rooms where<br/>snooze_time.time <= now
DB-->>SERVICE: Return expired snooze rooms
SERVICE->>DB: Mark snooze_queue_in_progress=true
loop For each expired room
SERVICE->>QUEUE: Add unsnooze job
QUEUE->>DB: Remove snooze_time
QUEUE->>DB: Restore to active queue
QUEUE->>SOCKET: Emit room update event
SOCKET-->>Dashboard: Update UI
end
๐ Source Filesโ
1. Cron Initializationโ
File: queue-manager/crons/conversations/support.snooze.js
Purpose: Schedule snooze checks every 10 seconds
Cron Pattern: */10 * * * * * (every 10 seconds)
Initialization:
const supportSnooze = require('../../services/conversations/support.snooze');
const cron = require('node-cron');
const logger = require('../../utilities/logger');
let inProgress = false;
exports.start = async () => {
try {
cron.schedule('*/10 * * * * *', async () => {
if (!inProgress) {
inProgress = true;
await supportSnooze();
inProgress = false;
}
});
} catch (err) {
logger.error({ initiator: 'QM/conversations/support-snooze', error: err });
}
};
In-Progress Lock: Prevents overlapping executions.
2. Service Processing (THE CORE LOGIC)โ
File: queue-manager/services/conversations/support.snooze.js
Purpose: Identify expired snooze rooms and add to unsnooze queue
Processing Logic:
const { SupportRoom } = require('../../models');
const Queue = require('../../queues/conversations/support.snooze');
module.exports = async () => {
try {
// 1. Query rooms with expired snooze time
const rooms = await SupportRoom.aggregate([
{
$match: {
'snooze_time.time': {
$lte: new Date(), // Snooze time expired
},
snooze_queue_in_progress: {
$ne: true, // Not already processing
},
},
},
]);
if (rooms.length) {
// 2. Collect room IDs
const ids = rooms.map(room => room._id);
// 3. Mark as processing to prevent duplicate jobs
await SupportRoom.updateMany({ _id: { $in: ids } }, { snooze_queue_in_progress: true });
// 4. Add each room to unsnooze queue
await Promise.all(
rooms.map(async room => {
try {
const queue = await Queue.start();
await queue.add(
{ id: room._id },
{
attempts: 10,
backoff: {
type: 'exponential',
delay: 1000,
},
},
);
} catch (err) {
// Reset processing flag on error
await SupportRoom.updateOne({ _id: room._id }, { snooze_queue_in_progress: false });
console.log(
`Error occured while removing snooze time for rooms`,
err.message,
err.stack,
);
}
}),
);
console.log('Processed snooze rooms.');
}
} catch (err) {
console.log(`Error occured while removing snooze time for rooms`, err.message, err.stack);
}
};
3. Queue Definitionโ
File: queue-manager/queues/conversations/support.snooze.js
Purpose: Bull queue configuration and room unsnooze execution
Queue Options:
{
attempts: 10, // 10 retry attempts
backoff: {
type: "exponential", // Exponential backoff
delay: 1000 // 1 second base delay
},
removeOnComplete: true, // Auto-cleanup
}
Job Processor: Removes snooze_time, restores room to active queue, emits socket events
๐๏ธ Collections Usedโ
support_roomsโ
- Operations: Read, Update
- Model:
shared/models/support-room.js - Usage Context:
- Query rooms with expired snooze_time
- Remove snooze_time field
- Reset snooze_queue_in_progress flag
- Restore room to active support queue
Key Fields:
snooze_time: Object withtimefield (Date when unsnooze should occur)snooze_queue_in_progress: Boolean lock to prevent duplicate processingstatus: Room status (open/snoozed/closed)assigned_to: Support agent assignment
๐ง Job Configurationโ
Queue Optionsโ
{
attempts: 10, // Maximum retry attempts
backoff: {
type: "exponential", // Exponential backoff strategy
delay: 1000 // 1 second base delay
},
removeOnComplete: true, // Auto-cleanup on success
}
Cron Scheduleโ
'*/10 * * * * *'; // Every 10 seconds
Frequency Rationale: 10-second intervals provide near-real-time unsnoozing while minimizing database queries.
๐ Processing Logic - Detailed Flowโ
Service Layer Processingโ
Service Function: module.exports (anonymous async function)
Purpose: Query expired snooze rooms and add to unsnooze queue
Processing Steps:
-
Query Expired Snooze Rooms
const rooms = await SupportRoom.aggregate([
{
$match: {
'snooze_time.time': { $lte: new Date() },
snooze_queue_in_progress: { $ne: true },
},
},
]);Matching Criteria:
snooze_time.time <= now: Snooze period has expiredsnooze_queue_in_progress != true: Not already being processed
-
Mark as Processing
const ids = rooms.map(room => room._id);
await SupportRoom.updateMany({ _id: { $in: ids } }, { snooze_queue_in_progress: true });Purpose: Prevent duplicate job creation if cron runs while jobs are being added
-
Add Unsnooze Jobs
await Promise.all(
rooms.map(async room => {
const queue = await Queue.start();
await queue.add(
{ id: room._id },
{
attempts: 10,
backoff: { type: 'exponential', delay: 1000 },
},
);
}),
); -
Error Recovery
catch (err) {
// Reset processing flag to allow retry on next cron run
await SupportRoom.updateOne(
{ _id: room._id },
{ snooze_queue_in_progress: false }
);
}
Queue Processingโ
Queue Processor: Inside queues/conversations/support.snooze.js
Purpose: Remove snooze state and restore room to active queue
Job Data Structure:
{
id: ObjectId; // SupportRoom document ID
}
Processing Steps:
-
Fetch Support Room
const room = await SupportRoom.findById(job.data.id);
if (!room) {
return { skipped: true, reason: 'Room not found' };
} -
Remove Snooze Time
await SupportRoom.updateOne(
{ _id: room._id },
{
$unset: { snooze_time: '' },
snooze_queue_in_progress: false,
status: 'open', // Restore to active status
},
); -
Emit Socket Event
// Notify dashboard of room update
io.to(`support-${room.account_id}`).emit('support_room_updated', {
room_id: room._id,
action: 'unsnoozed',
timestamp: new Date(),
}); -
Return Result
return {
success: true,
room_id: room._id,
unsnoozed_at: new Date(),
};
Error Handling in Flowโ
Service Layer Errors:
try {
const queue = await Queue.start();
await queue.add({ id: room._id }, { attempts: 10, backoff: {...} });
} catch (err) {
// Reset flag to allow retry
await SupportRoom.updateOne(
{ _id: room._id },
{ snooze_queue_in_progress: false }
);
console.log(`Error occured while removing snooze time for rooms`, err.message);
}
Queue Processor Errors:
// Bull automatically retries based on attempts config (10 attempts)
queue.on('failed', async (job, err) => {
if (job.attemptsMade >= job.opts.attempts) {
// Max retries exceeded - reset flag
await SupportRoom.updateOne(
{ _id: job.data.id },
{
snooze_queue_in_progress: false,
unsnooze_error: err.message,
},
);
}
});
Retry Strategy: 10 attempts with exponential backoff starting at 1 second
๐จ Error Handlingโ
Common Error Scenariosโ
Room Not Foundโ
const room = await SupportRoom.findById(job.data.id);
if (!room) {
// Room was deleted - job is idempotent
return { skipped: true, reason: 'Room already deleted' };
}
Socket Emit Failureโ
try {
io.to(`support-${room.account_id}`).emit('support_room_updated', data);
} catch (error) {
// Socket failure shouldn't block unsnooze
console.error('Socket emit failed:', error.message);
// Continue processing
}
Database Connection Errorโ
try {
await SupportRoom.updateOne({ _id: room._id }, { $unset: { snooze_time: '' } });
} catch (error) {
console.error('Database update failed:', error.message);
throw error; // Trigger retry
}
Retry Strategyโ
{
attempts: 10, // Maximum 10 attempts
backoff: {
type: "exponential", // Exponential backoff
delay: 1000 // 1 second base delay
}
}
Backoff Schedule:
- Attempt 1: Immediate
- Attempt 2: 1 second
- Attempt 3: 2 seconds
- Attempt 4: 4 seconds
- Attempt 5-10: Progressive exponential backoff (8s, 16s, 32s, 64s, 128s, 256s)
๐ Monitoring & Loggingโ
Success Loggingโ
console.log('Processed snooze rooms.');
console.log(`Unsnoozed ${rooms.length} rooms`);
Error Loggingโ
console.log(`Error occured while removing snooze time for rooms`, err.message, err.stack);
Performance Metricsโ
- Average Processing Time: ~500ms per room
- Success Rate: ~99%
- Retry Rate: ~1%
- Typical Volume: 10-50 rooms per day
๐ Integration Pointsโ
Triggers This Jobโ
- Cron Schedule: Every 10 seconds automatically
- Snooze Feature: Support agents snooze rooms via dashboard
- Manual Trigger: Via API endpoint (if QM_HOOKS=true)
Data Dependenciesโ
- SupportRoom collection: Must have rooms with snooze_time set
- General Socket Service: For real-time UI updates
Related Jobsโ
- Support Notification: May trigger notifications when room becomes active
- Support Communication Check: Monitors active room health
โ ๏ธ Important Notesโ
Side Effectsโ
- โ ๏ธ Database Updates: Removes snooze_time field from rooms
- โ ๏ธ Socket Events: Emits real-time updates to connected dashboards
- โ ๏ธ Queue Visibility: Restores rooms to active support queue
- โ ๏ธ Agent Notifications: May trigger desktop/mobile notifications
Performance Considerationsโ
- 10-Second Intervals: Balance between responsiveness and database load
- In-Progress Lock: Prevents duplicate processing during overlaps
- Exponential Backoff: Handles temporary failures gracefully
- Parallel Processing: All rooms processed concurrently
Maintenance Notesโ
- Stuck Processing Flags: Monitor for rooms with
snooze_queue_in_progress=truefor extended periods - Snooze Time Format: Must be
{ time: Date }object structure - Socket Connection: Requires General Socket service to be running
- Timezone Handling: All snooze times stored in UTC
๐งช Testingโ
Manual Triggerโ
# Via API (if QM_HOOKS=true)
POST http://localhost:6002/api/trigger/conversations/support-snooze
Create Test Snoozed Roomโ
// Snooze room for 1 minute
await SupportRoom.updateOne(
{ _id: testRoomId },
{
snooze_time: {
time: new Date(Date.now() + 60 * 1000), // 1 minute from now
set_by: testUserId,
set_at: new Date(),
},
status: 'snoozed',
},
);
// Wait 1 minute, then check for unsnooze
setTimeout(async () => {
const room = await SupportRoom.findById(testRoomId);
console.log('Snooze removed:', !room.snooze_time);
console.log('Status restored:', room.status === 'open');
}, 65000);
Monitor Snooze Queueโ
// Count rooms pending unsnooze
const pendingUnsnooze = await SupportRoom.countDocuments({
'snooze_time.time': { $lte: new Date() },
snooze_queue_in_progress: false,
});
console.log('Rooms pending unsnooze:', pendingUnsnooze);
// Count rooms currently processing
const inProgress = await SupportRoom.countDocuments({
snooze_queue_in_progress: true,
});
console.log('Rooms currently unsnoozing:', inProgress);
Job Type: Scheduled
Execution Frequency: Every 10 seconds
Average Duration: 500ms per room
Status: Active