KyoMel / Yogyakarta, Indonesia

Term 090090 / 344

Dead Letter Queue(DLQ)

Month
October 2025
Day
20
Source
2025/october-term.md

Definition:

Dead Letter Queue (DLQ) is a service-side queue that stores messages that couldn’t be processed successfully after multiple retry attempts. When a message fails repeatedly due to errors, corruption, or processing issues, it gets moved to the DLQ instead of being lost or continuously retried, allowing developers to investigate and handle problematic messages separately.

Key Properties:

  • Failure isolation: Separates problematic messages from healthy flow
  • Retry exhaustion: Triggered after max retry attempts reached
  • Message preservation: Keeps failed messages for analysis
  • System stability: Prevents infinite retry loops
  • Manual intervention: Allows debugging and reprocessing

How It Works:

  • Message fails processing in main queue
  • Retry mechanism attempts reprocessing (1-N times)
  • Still failing? Message moved to DLQ
  • Continue processing other messages normally
  • Investigate DLQ messages separately for root cause analysis and resolution

Example:

Email Service

class EmailService {
  constructor() {
    this.emailQueue = new Queue('emails');
    this.emailDLQ = new Queue('emails-dlq');
    this.maxRetries = 5;
  }

  async sendEmailWithDLQ(emailData) {
    const message = {
      id: generateUniqueId(),
      body: emailData,
      retryCount: 0,
      createdAt: Date.now()
    };

    await this.emailQueue.send(message);
  }

  async processEmailQueue() {
    while (true) {
      const message = await this.emailQueue.receive();

      if (message) {
        await this.handleEmailMessage(message);
      }

      await this.sleep(1000);
    }
  }

  async handleEmailMessage(message) {
    try {
      const email = message.body;

      // Validate email
      if (!this.isValidEmail(email)) {
        throw new Error('Invalid email format');
      }

      // Send email
      await this.sendEmail(email);

      console.log(`📧 Email sent to ${email.to}`);

    } catch (error) {
      const retryCount = message.retryCount || 0;

      if (retryCount >= this.maxRetries) {
        // Move to DLQ with error details
        await this.emailDLQ.send({
          originalEmail: message.body,
          errorType: error.name,
          errorMessage: error.message,
          retryCount: retryCount,
          failedAt: new Date().toISOString(),

          // Add context for debugging
          serverResponse: error.response,
          emailProvider: 'sendgrid',
          lastAttempt: Date.now()
        });

        console.log(`💀 Email moved to DLQ: ${email.to}`);

        // Alert ops team
        await this.alertOpsTeam({
          message: 'Email moved to Dead Letter Queue',
          recipient: message.body.to,
          error: error.message
        });

      } else {
        // Retry with exponential backoff
        message.retryCount = retryCount + 1;
        message.retryAt = Date.now() + (1000 * Math.pow(2, retryCount));

        setTimeout(async () => {
          await this.emailQueue.send(message);
        }, 1000 * Math.pow(2, retryCount));

        console.log(`🔄 Email retry ${retryCount + 1}/${this.maxRetries}: ${message.body.to}`);
      }
    }
  }

  // DLQ Management Dashboard
  async getDLQStats() {
    const dlqMessages = await this.emailDLQ.getAllMessages();

    const stats = {
      totalFailed: dlqMessages.length,
      failureTypes: {},
      oldestFailure: null,
      recentFailures: 0
    };

    const now = Date.now();
    const oneHourAgo = now - (60 * 60 * 1000);

    dlqMessages.forEach(msg => {
      // Count failure types
      const errorType = msg.errorType || 'Unknown';
      stats.failureTypes[errorType] = (stats.failureTypes[errorType] || 0) + 1;

      // Find oldest failure
      if (!stats.oldestFailure || msg.failedAt < stats.oldestFailure) {
        stats.oldestFailure = msg.failedAt;
      }

      // Count recent failures
      if (msg.lastAttempt > oneHourAgo) {
        stats.recentFailures++;
      }
    });

    return stats;
  }

  async reprocessDLQMessages() {
    const dlqMessages = await this.emailDLQ.getAllMessages();
    let reprocessed = 0;

    for (const msg of dlqMessages) {
      try {
        // Attempt to fix common issues
        const fixedEmail = await this.fixEmailIssues(msg.originalEmail);

        if (fixedEmail) {
          // Send back to main queue for reprocessing
          await this.sendEmailWithDLQ(fixedEmail);

          // Remove from DLQ
          await this.emailDLQ.delete(msg);

          reprocessed++;
          console.log(`🔧 Reprocessed email: ${fixedEmail.to}`);
        }
      } catch (error) {
        console.log(`❌ Cannot fix email: ${error.message}`);
      }
    }

    return { reprocessed, remaining: dlqMessages.length - reprocessed };
  }
}

// Usage
const emailService = new EmailService();

// Start processing
emailService.processEmailQueue();

// Monitor DLQ (run periodically)
setInterval(async () => {
  const stats = await emailService.getDLQStats();
  console.log('DLQ Stats:', stats);

  if (stats.totalFailed > 10) {
    console.log('🚨 High number of failed emails in DLQ!');
  }
}, 60000); // Check every minute

Keep reading