Deleting mail from the mail queue
Sendmail does not provide a command-line argument to remove messages
from the mail queue. It may be necessary to manually remove messages
from the mail queue rather than allowing Sendmail to attempt redelivery
of messages for Timeout.queureturn days (5, by default).
The proper way to remove messages from the mail queue is to use the qtool.pl program included in the
contrib subdirectory of the Sendmail source code distribution. qtool.pl
uses the same file locking mechanism as Sendmail.
Removing "double bounce" messages
The following is a Perl script that calls /usr/local/bin/qtool.pl
to remove "double bounce" messages. A "double bounce" is a message that
is addressed to a non-existent user and that is sent from an invalid
return address. Busy mail relays often have hundreds to thousands of these messages.
The script below will delete a queued message if it is
(1) "deferred" (unable to be returned to the sender), (2) being
sent from our postmaster email address, and (3) the subject is
unique to delivery failure notifications.
#!/usr/bin/perl
use strict;
my $qtool = "/usr/local/bin/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 $queue_id = $1;
my $deferred = 0;
my $from_postmaster = 0;
my $delivery_failure = 0;
my $double_bounce = 0;
open (QF_FILE, $_);
while(<QF_FILE>) {
$deferred = 1 if ( /^MDeferred/ );
$from_postmaster = 1 if ( /^S<>$/ );
$delivery_failure = 1 if \
( /^H\?\?Subject: DELIVERY FAILURE: (User|Recipient)/ );
if ( $deferred && $from_postmaster && $delivery_failure ) {
$double_bounce = 1;
last;
}
}
close (QF_FILE);
if ($double_bounce) {
print "Removing $queue_id...\n";
system "$qtool", "-d", $qf_file;
$messages_removed++;
}
}
}
print "\n$messages_removed total \"double bounce\" message(s) removed from ";
print "mail queue.\n";
Queued mail by domain
The following Perl
script will show all queued mail by domain. A message may be counted
more than once if it has multiple envelope recipients from different
domains.
#!/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(<QF_FILE>) {
# 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";
}
Removing mail by domain
The following Perl script will remove all mail in the mail queue addressed to domain. Messages with multiple envelope recipients to different domains will not be deleted.
#!/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/local/bin/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(<QF_FILE>) {
# 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";
Queued mail by email address
The following Perl
script will show all queued mail by email address. A message may be
counted more than once if it has multiple envelope recipients.
#!/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(<QF_FILE>) {
# 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";
}
Removing mail by email address
The following Perl script will remove all mail in the mail queue addressed to email_address. Messages with multiple envelope recipients will not be deleted.
#!/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/local/bin/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(<QF_FILE>) {
# 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";
Older notes
Note: the preferred method of queue removal is to use qtool.pl as illustrated above.
In order to remove mail from the queue, you have to delete the df* and qf* files from your mail
queue directory, generally /var/spool/mqueue.
The qf* file is the header of
the message and the control file, and the df* file is the body of the message.
I wrote the following script to move undeliverable email in our
/var/spool/mqueue mail queue to an alternate /tmp/mqueue directory.
#!/bin/sh
if [ -z $@ ] ; then
echo "Usage: $0
email_address"
exit 1
fi
for i in `(cd /var/spool/mqueue;
grep -l "To:.*$1" qf* | cut -c3-)`
do
mv
/var/spool/mqueue/*$i /tmp/mqueue
done
If you have multiple mail queues, such as q1, q2, q3, q4, and q5,
you can use the following script:
#!/bin/sh
if [ -z $@ ] ; then
echo "Usage: $0
email_address"
exit 1
fi
for i in q1 q2 q3 q4 q5
do
for j in `(cd
/var/spool/mqueue/$i; grep -l "To:.*$1" qf* | cut -c3-)`
do
mv
/var/spool/mqueue/$i/*$j /tmp/mqueue
done
done
For example, running the script while passing the command-line
argument badsender@baddomain.com will
look for each qf* file in the
mail queue containing To:.*badsender@baddomain.com.
The regular
expression .* will match zero
or more occurrences of any characters, numbers, or whitespace. For
example, it would match:
To: badsender@baddomain.com
To: Bad Sender
<badsender@baddomain.com>
The script then moves any other files (i.e. the
body of the message) in the mail queue with the same Sendmail message
ID
to the alternate directory. It does this with the cut -c3- command, as the Sendmail
message ID is the 3rd through the last character.
The mail is moved to /tmp/mqueue.
If you
are confident that you do not want the messages, you can delete them
from
this directory, or you could change the script to remove the files.
Back
to brandonhutchinson.com.
Last modified: 07/26/2005