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.
