The Best Price for IPv4/IPv6 Lease – Any RIR & Any Geo-LocationOrder Now
Hostperl

Setup Email Queue Monitoring on Ubuntu VPS: Complete System Guide

By Raman Kumar

Share:

Updated on Jun 9, 2026

Setup Email Queue Monitoring on Ubuntu VPS: Complete System Guide

Why Email Queue Monitoring Matters for Your VPS

Email delivery failures can sink your business reputation faster than a server crash. When your VPS handles hundreds or thousands of emails daily, a backed-up mail queue often signals deeper problems: overloaded servers, spam attacks, or misconfigured mail relays.

This guide shows you how to setup email queue monitoring on Ubuntu VPS systems. You'll track queue sizes, spot delivery bottlenecks, and get alerts before small issues become major outages.

We'll configure Postfix queue monitoring with custom scripts, automate alerts, and build a simple dashboard to track your mail server's health. This works whether you're running a single-domain setup or managing multiple client sites.

Prerequisites and Server Requirements

Your Ubuntu VPS needs these basics:

  • Ubuntu 20.04 LTS or newer
  • Postfix mail server already installed and configured
  • Root or sudo access to the server
  • Basic familiarity with Linux command line
  • At least 1GB RAM and 10GB storage space

You'll also need a working email system. If you haven't set up Postfix yet, check our complete Postfix and Dovecot setup guide first.

Most Hostperl VPS hosting plans include sufficient resources for email queue monitoring alongside your existing mail server setup.

Install Required Monitoring Tools

Start by updating your system packages and installing the monitoring tools:

sudo apt update
sudo apt install -y mailutils postfix-policyd-spf-python python3-pip
pip3 install psutil matplotlib

These packages provide the foundation for queue monitoring. The mailutils package includes mailq for examining queues, while Python libraries help with data processing and visualization.

Create a dedicated directory for your monitoring scripts:

sudo mkdir -p /opt/mail-monitor
sudo chown $USER:$USER /opt/mail-monitor
cd /opt/mail-monitor

Configure Basic Queue Monitoring Script

Create a Python script to monitor Postfix queue status. This script checks queue sizes, identifies stuck messages, and generates alerts:

nano queue_monitor.py

Add this monitoring script:

#!/usr/bin/env python3
import subprocess
import re
import datetime
import json
import smtplib
from email.mime.text import MIMEText

class PostfixQueueMonitor:
    def __init__(self, alert_threshold=50, max_age_hours=2):
        self.alert_threshold = alert_threshold
        self.max_age_hours = max_age_hours
        self.log_file = '/var/log/mail-queue-monitor.log'
    
    def get_queue_stats(self):
        try:
            result = subprocess.run(['mailq'], capture_output=True, text=True)
            output = result.stdout
            
            # Count total messages
            total_match = re.search(r'-- (\d+) Kbytes in (\d+) Request', output)
            if total_match:
                total_messages = int(total_match.group(2))
            else:
                total_messages = 0
            
            # Parse individual messages
            deferred = 0
            active = 0
            hold = 0
            
            lines = output.split('\n')
            for line in lines:
                if 'MAILER-DAEMON' in line or 'deferred' in line.lower():
                    deferred += 1
                elif line.startswith(' ') and '@' in line:
                    active += 1
                elif 'hold' in line.lower():
                    hold += 1
            
            return {
                'total': total_messages,
                'deferred': deferred,
                'active': active,
                'hold': hold,
                'timestamp': datetime.datetime.now().isoformat()
            }
        except Exception as e:
            self.log_error(f"Error getting queue stats: {e}")
            return None
    
    def check_old_messages(self):
        try:
            result = subprocess.run(['postqueue', '-p'], capture_output=True, text=True)
            old_messages = []
            
            lines = result.stdout.split('\n')
            for line in lines:
                # Look for timestamp patterns
                if re.match(r'^[A-F0-9]+', line):
                    parts = line.split()
                    if len(parts) >= 3:
                        date_str = ' '.join(parts[1:3])
                        try:
                            msg_time = datetime.datetime.strptime(date_str, '%a %b %d')
                            # Add current year
                            msg_time = msg_time.replace(year=datetime.datetime.now().year)
                            age_hours = (datetime.datetime.now() - msg_time).total_seconds() / 3600
                            
                            if age_hours > self.max_age_hours:
                                old_messages.append({
                                    'queue_id': parts[0],
                                    'age_hours': round(age_hours, 2),
                                    'size': parts[1] if len(parts) > 1 else 'unknown'
                                })
                        except ValueError:
                            continue
            
            return old_messages
        except Exception as e:
            self.log_error(f"Error checking old messages: {e}")
            return []
    
    def send_alert(self, message):
        # Configure your SMTP settings here
        smtp_server = 'localhost'
        smtp_port = 25
        from_email = 'monitor@yourdomain.com'
        to_email = 'admin@yourdomain.com'
        
        try:
            msg = MIMEText(message)
            msg['Subject'] = 'Mail Queue Alert'
            msg['From'] = from_email
            msg['To'] = to_email
            
            with smtplib.SMTP(smtp_server, smtp_port) as server:
                server.send_message(msg)
            
            self.log_message("Alert sent successfully")
        except Exception as e:
            self.log_error(f"Failed to send alert: {e}")
    
    def log_message(self, message):
        timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        with open(self.log_file, 'a') as f:
            f.write(f"{timestamp} - INFO - {message}\n")
    
    def log_error(self, message):
        timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        with open(self.log_file, 'a') as f:
            f.write(f"{timestamp} - ERROR - {message}\n")
    
    def run_check(self):
        stats = self.get_queue_stats()
        if not stats:
            return
        
        self.log_message(f"Queue check: {stats}")
        
        # Check for high queue volume
        if stats['total'] > self.alert_threshold:
            alert_msg = f"High mail queue volume: {stats['total']} messages\n"
            alert_msg += f"Deferred: {stats['deferred']}, Active: {stats['active']}, Hold: {stats['hold']}"
            self.send_alert(alert_msg)
        
        # Check for old messages
        old_messages = self.check_old_messages()
        if old_messages:
            alert_msg = f"Found {len(old_messages)} old messages in queue:\n"
            for msg in old_messages[:5]:  # Show first 5
                alert_msg += f"Queue ID: {msg['queue_id']}, Age: {msg['age_hours']} hours\n"
            self.send_alert(alert_msg)

if __name__ == '__main__':
    monitor = PostfixQueueMonitor(alert_threshold=100, max_age_hours=6)
    monitor.run_check()

Make the script executable:

chmod +x queue_monitor.py

Set Up Automated Queue Monitoring

Configure the monitoring script to run automatically every 5 minutes. Create a cron job:

crontab -e

Add this line:

*/5 * * * * /usr/bin/python3 /opt/mail-monitor/queue_monitor.py

The script will now check your mail queue every 5 minutes and send alerts when issues occur. You can adjust the frequency based on your server's email volume.

Create a log rotation configuration to prevent log files from growing too large:

sudo nano /etc/logrotate.d/mail-queue-monitor

Add this configuration:

/var/log/mail-queue-monitor.log {
    weekly
    rotate 4
    compress
    missingok
    notifempty
    create 0644 root root
}

Create Queue Statistics Dashboard

Build a simple web dashboard to visualize queue statistics. Create a PHP script that displays current queue status:

nano /var/www/html/queue-status.php

Add this dashboard code:

&1');
    
    $stats = [
        'total' => 0,
        'deferred' => 0,
        'active' => 0,
        'timestamp' => date('Y-m-d H:i:s')
    ];
    
    // Parse mailq output
    if (preg_match('/-- (\d+) Kbytes in (\d+) Request/', $output, $matches)) {
        $stats['total'] = (int)$matches[2];
    }
    
    $lines = explode("\n", $output);
    foreach ($lines as $line) {
        if (strpos($line, 'deferred') !== false || strpos($line, 'MAILER-DAEMON') !== false) {
            $stats['deferred']++;
        } elseif (preg_match('/^\s+.*@/', $line)) {
            $stats['active']++;
        }
    }
    
    return $stats;
}

function getRecentLogs() {
    $logFile = '/var/log/mail-queue-monitor.log';
    if (!file_exists($logFile)) {
        return [];
    }
    
    $logs = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    return array_slice($logs, -20); // Last 20 lines
}

$stats = getQueueStats();
$logs = getRecentLogs();
?>


    

Mail Queue Status Dashboard

100): ?>
Warning: High queue volume detected ( messages)
Status: Queue levels normal
Total Messages
Deferred
Active
Last Updated

Recent Monitoring Log

This dashboard provides a real-time view of your mail queue status. Access it via your server's IP address or domain name at /queue-status.php.

Configure Advanced Alert Rules

Enhance your monitoring with more sophisticated alerting rules. Create a configuration file for custom thresholds:

nano /opt/mail-monitor/config.json

Add this configuration:

{
    "thresholds": {
        "queue_size_warning": 50,
        "queue_size_critical": 200,
        "message_age_warning_hours": 2,
        "message_age_critical_hours": 8,
        "deferred_percentage_warning": 10,
        "deferred_percentage_critical": 25
    },
    "alerts": {
        "email": {
            "enabled": true,
            "smtp_server": "localhost",
            "smtp_port": 25,
            "from": "monitor@yourdomain.com",
            "to": ["admin@yourdomain.com"]
        },
        "slack": {
            "enabled": false,
            "webhook_url": ""
        }
    },
    "monitoring": {
        "check_interval_minutes": 5,
        "log_retention_days": 30
    }
}

This configuration allows you to fine-tune alert thresholds based on your server's typical email volume and requirements.

Running email infrastructure requires reliable hosting with proper monitoring capabilities. Hostperl VPS hosting provides the performance and control needed for email servers with full root access for custom monitoring setups.

Monitor Postfix Logs for Delivery Issues

Complement queue monitoring with log analysis to catch delivery problems early. Create a log parsing script:

nano /opt/mail-monitor/log_analyzer.py

Add this log analysis code:

#!/usr/bin/env python3
import re
import subprocess
from collections import defaultdict
from datetime import datetime, timedelta

def analyze_postfix_logs(hours_back=1):
    # Get recent log entries
    since_time = datetime.now() - timedelta(hours=hours_back)
    since_str = since_time.strftime('%b %d %H:%M')
    
    try:
        cmd = f"grep '{since_str[:6]}' /var/log/mail.log | tail -1000"
        result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
        
        stats = {
            'bounced': 0,
            'deferred': 0,
            'sent': 0,
            'rejected': 0,
            'bounce_reasons': defaultdict(int),
            'defer_reasons': defaultdict(int)
        }
        
        for line in result.stdout.split('\n'):
            if 'bounced' in line:
                stats['bounced'] += 1
                # Extract bounce reason
                match = re.search(r'\((.*?)\)', line)
                if match:
                    stats['bounce_reasons'][match.group(1)] += 1
            
            elif 'deferred' in line:
                stats['deferred'] += 1
                # Extract defer reason
                match = re.search(r'\((.*?)\)', line)
                if match:
                    stats['defer_reasons'][match.group(1)] += 1
            
            elif 'status=sent' in line:
                stats['sent'] += 1
            
            elif 'reject:' in line or 'blocked' in line:
                stats['rejected'] += 1
        
        return stats
    except Exception as e:
        print(f"Error analyzing logs: {e}")
        return None

def generate_report(stats):
    if not stats:
        return "Unable to generate report"
    
    report = f"\nMail Server Report (Last Hour):\n"
    report += f"=====================================\n"
    report += f"Sent: {stats['sent']}\n"
    report += f"Deferred: {stats['deferred']}\n"
    report += f"Bounced: {stats['bounced']}\n"
    report += f"Rejected: {stats['rejected']}\n\n"
    
    if stats['defer_reasons']:
        report += "Top Defer Reasons:\n"
        for reason, count in sorted(stats['defer_reasons'].items(), key=lambda x: x[1], reverse=True)[:3]:
            report += f"  {reason}: {count}\n"
        report += "\n"
    
    if stats['bounce_reasons']:
        report += "Top Bounce Reasons:\n"
        for reason, count in sorted(stats['bounce_reasons'].items(), key=lambda x: x[1], reverse=True)[:3]:
            report += f"  {reason}: {count}\n"
    
    return report

if __name__ == '__main__':
    stats = analyze_postfix_logs(1)
    print(generate_report(stats))

Run this script manually to get detailed delivery statistics:

python3 /opt/mail-monitor/log_analyzer.py

Troubleshoot Common Queue Issues

When your monitoring alerts trigger, use these troubleshooting steps to identify and resolve queue problems quickly.

High Queue Volume: Check if your server is under a spam attack or experiencing delivery issues to a specific domain. Use postqueue -p | grep -c "example.com" to count messages for specific domains.

Stuck Messages: Messages older than 6 hours usually indicate configuration problems. Check recipient domains, MX records, and relay settings. Use postcat -vq QUEUE_ID to examine specific messages.

Repeated Deferrals: Often caused by temporary DNS issues, rate limiting by recipient servers, or authentication problems. Review error messages in your monitoring logs for patterns.

For more complex email delivery issues, our Postfix queue management tutorial covers advanced troubleshooting techniques.

Optimize Queue Performance

Fine-tune Postfix settings to handle email queues more efficiently. Edit the main Postfix configuration:

sudo nano /etc/postfix/main.cf

Add or modify these performance settings:

# Queue processing
maximal_queue_lifetime = 5d
bounce_queue_lifetime = 5d
delay_warning_time = 2h

# Delivery concurrency
default_destination_concurrency_limit = 5
smtp_destination_concurrency_limit = 10

# Queue management
qmgr_message_active_limit = 20000
qmgr_message_recipient_limit = 20000

Restart Postfix to apply changes:

sudo systemctl restart postfix

These settings balance delivery performance with resource usage. Adjust values based on your server capacity and email volume.

Frequently Asked Questions

How often should I check email queue status?

For most VPS servers, checking every 5 minutes provides good coverage without excessive resource usage. High-volume servers may benefit from more frequent monitoring, while low-traffic servers can use 10-15 minute intervals.

What queue size indicates a problem?

Queue size thresholds depend on your typical email volume. Start with alerts at 50 messages for small servers and 200+ for busy servers. Monitor trends over time to establish baseline values for your specific use case.

Can I monitor multiple Postfix servers from one dashboard?

Yes, modify the monitoring script to accept server parameters and collect data from multiple VPS instances. Store results in a central database or shared storage for consolidated reporting.

How do I handle false positive alerts?

Implement alert suppression for known issues (like scheduled maintenance) and adjust thresholds based on historical data. Consider adding alert delays for transient spikes that resolve quickly.

What's the best way to archive monitoring data?

Export queue statistics to CSV files daily, compress older data, and consider using time-series databases like InfluxDB for long-term storage and advanced analytics on larger deployments.