Skip to main content

โฐ 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:

  1. Cron Initialization: queue-manager/crons/conversations/support.snooze.js
  2. Service Processing: queue-manager/services/conversations/support.snooze.js
  3. 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 with time field (Date when unsnooze should occur)
  • snooze_queue_in_progress: Boolean lock to prevent duplicate processing
  • status: 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:

  1. 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 expired
    • snooze_queue_in_progress != true: Not already being processed
  2. 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

  3. 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 },
    },
    );
    }),
    );
  4. 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:

  1. Fetch Support Room

    const room = await SupportRoom.findById(job.data.id);
    if (!room) {
    return { skipped: true, reason: 'Room not found' };
    }
  2. Remove Snooze Time

    await SupportRoom.updateOne(
    { _id: room._id },
    {
    $unset: { snooze_time: '' },
    snooze_queue_in_progress: false,
    status: 'open', // Restore to active status
    },
    );
  3. 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(),
    });
  4. 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
  • 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=true for 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

๐Ÿ’ฌ

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