Delete messages from mqueue and mailq

In larger organizations, your smarthost/default SMTP gateways mailq can become overloaded with unwanted and undeliverable messages. If you have enough of them, it will add a lot of CPU and disk IO load as it tries to resend/reprocess Undeliverable messages. We have a few systems that if left unchecked would try and reprocess 100K messages every 5 min. The tmp mail files are typically located /var/spool/mqueue and directly deleting these is not the correct manner and opening each one to verify deletion will likely drive you mad. I have added four scripts that will help you manage the stagnant mail you may have accumulating in your mailq. These scripts will list the mail per target address, allow you to delete mail for a single user or an entire domain.

 

Using the below script “who-mail.sh”, you can determine what is causing the mailq to fill, which SMTP address and how many email messages exist in your mailq for delivery.  (All of these tools can be downloaded from here)

 

#!/usr/bin/perl

use strict;

my $mqueue_directory = "/var/spool/mqueue";
my %occurrences;

use File::Find;

# Recursively find all files and directories in $mqueue_directory
find( \&wanted, $mqueue_directory );

sub wanted {

    # Is this a qf* file?
    if (/^qf\w{14}/) {
        open( QF_FILE, $_ );
        while () {

            # Lines beginning with R contain an envelope recipient
            if (/^R.*:<(.*)>$/) {
                my $domain = lc($1);

                # Add 1 to the %occurrences hash
                $occurrences{$domain}++;
            }
        }
    }
}

# Subroutine to sort hash by ascending value
sub hashValueAscendingNum {
    $occurrences{$a} <=> $occurrences{$b};
}

# Print sorted results
foreach my $key ( sort hashValueAscendingNum ( keys(%occurrences) ) ) {
    print "$occurrences{$key} $key\n";
}

This prints to screen the qty and full email address of messages in queue. Looking at the bottom few rows you will typically find your main source of mailq stress.

Using the below script “purge-domain-mail.sh” along with the domain name that is causing the large mailq load, will delete all messages form that domain using the qtool.pl from sendmail. This script simply automates the feed into qtools.pl for you. Additionally it will obey with the FQDN given and will only purge exact matching FQDN. Meaning, if you run “./purge-domain-mail.sh server1.microsoft.com” it will delete all messages for ThirdLD server1.microsoft.com but leave any message for the SLD microsoft.com.

 

#!/usr/bin/perl

use strict;

# Exit immediately if domain was not specified as command-line argument
if ( !( defined( $ARGV[0] ) ) ) {
    ( my $basename = $0 ) =~ s!^.*/!!;
    print "Usage: $basename domain\n";
    exit 1;
}

# Convert domain supplied as command-line argument to lowercase
my $domain_to_remove = lc( $ARGV[0] );

my $qtool            = "/usr/share/sendmail/qtool.pl";
my $mqueue_directory = "/var/spool/mqueue";
my $messages_removed = 0;

use File::Find;

# Recursively find all files and directories in $mqueue_directory
find( \&wanted, $mqueue_directory );

sub wanted {

    # Is this a qf* file?
    if (/^qf\w{14}/) {
        my $QF_FILE             = $_;
        my $envelope_recipients = 0;
        my $match               = 1;
        open( QF_FILE, $_ );
        while () {

            # If any of the envelope recipients contain a domain other than
            # $domain_to_remove, do not match the message
            if (/^R.*:<.*\@(.*)>$/) {
                my $recipient_domain = lc($1);
                $envelope_recipients++;
                if ( $recipient_domain ne $domain_to_remove ) {
                    $match = 0;
                    last;
                }
            }
        }
        close(QF_FILE);

       # $QF_FILE may not contain an envelope recipient at the time it is opened
       # and read. Do not match $QF_FILE in that case.
        if ( $match == 1 && $envelope_recipients != 0 ) {
            print "Removing $QF_FILE...\n";
            system "$qtool", "-d", $QF_FILE;
            $messages_removed++;
        }
    }
}

print "$messages_removed total message(s) removed from mail queue.\n";

When this is run, it prints to screen the qf file name that was deleted and then the grand total of deleted message at the end of the script.

 

Next up, we can delete mail for just one SMTP address and not the entire domain. Simply run “./purge-who-mail.sh user@domain.com” This like the purge-domain-mail.sh will delete the messages, keep sendmail up to speed and flush out the mqueue directory of unwanted junk.

 

#!/usr/bin/perl

use strict;

# Exit immediately if email_address was not specified as command-line argument
if ( !( defined( $ARGV[0] ) ) ) {
    ( my $basename = $0 ) =~ s!^.*/!!;
    print "Usage: $basename email_address\n";
    exit 1;
}

# Convert email address supplied as command-line argument to lowercase
my $address_to_remove = lc( $ARGV[0] );

my $qtool            = "/usr/share/sendmail/qtool.pl";
my $mqueue_directory = "/var/spool/mqueue";
my $messages_removed = 0;

use File::Find;

# Recursively find all files and directories in $mqueue_directory
find( \&wanted, $mqueue_directory );

sub wanted {

    # Is this a qf* file?
    if (/^qf\w{14}/) {
        my $QF_FILE             = $_;
        my $envelope_recipients = 0;
        my $match               = 1;
        open( QF_FILE, $_ );
        while () {

         # If any of the envelope recipients contain an email address other than
         # $address_to_remove, do not match the message
            if (/^R.*:<(.*)>$/) {
                my $recipient_address = lc($1);
                $envelope_recipients++;
                if ( $recipient_address ne $address_to_remove ) {
                    $match = 0;
                    last;
                }
            }
        }
        close(QF_FILE);

       # $QF_FILE may not contain an envelope recipient at the time it is opened
       # and read. Do not match $QF_FILE in that case.
        if ( $match == 1 && $envelope_recipients != 0 ) {
            print "Removing $QF_FILE...\n";
            system "$qtool", "-d", $QF_FILE;
            $messages_removed++;
        }
    }
}

print "$messages_removed total message(s) removed from mail queue.\n";

Remember you can also also run “watch mailq” for a little more real time view on your mail queue and keep up to date with the mail flow in your system.