I get bounced mails. Lots of them. And most of them aren't even for mails that originated within my system. So I decided to write me a Mail::Audit plugin, which decides whether a mail is actually a bounce, to sort the bounce into a special folder. Later on, I'll add a check against the message ID of the mail and delete all mails that were bounced but did not originate on my system...

But I only have a shallow understanding of email headers, and maybe I'm doing this in a completely wrong way, and there is a much easier way to specify what I want. So without further ado, here is my module. Comments welcome!

package Mail::Audit::Bounce; use Mail::Audit; use vars qw(@VERSION); $VERSION = '0.01'; 1; =head 1 Mail::Audit::Bounce - Recognize a mail as a bounce mail =cut package Mail::Audit; use strict; my $content_type = qr(report-type=delivery-status); my $sender = qr(Mail Delivery Subsystem <MAILER-DAEMON\@); my @headers = ( 'X-Virus-Found' => [ qr/./, "Virus found"], # header m +ust be present 'X-Tnz-Problem-Type' => [ qr/^40$/, "Virus found"], 'X-Failed-Recipients' => [ qr/./, undef], 'Auto-Submitted' => [ qr<auto-generated \(failure\)>,undef], 'Content-Type' => [ qr<multipart/report; report-type=delivery +-status;>ism,undef], 'Content-Type' => [ qr<multipart/report; report-type=(["'])de +livery-status\1;>ism,undef], 'Received' => [ qr<\(qmail \d+ invoked for bounce\);>sm, +undef], ); sub __scan_delivery_report { my ($self) = @_; my @result; my @body; for my $line (@{$self->body}) { push @body, split /\n/, $line; }; my $i = 0; while ($i < @headers) { my ($header,$r) = (@headers[$i,$i+1]); my ($content,$reason) = @$r; my @h = ($self->head->get($header), map { /^$header: (.*)$/ ? $1 : + () } @body); for my $line (@h) { push @result, $reason if ($line =~ $content); }; $i += 2; }; @result; }; sub __mime_parts { my ($self,$content_type) = @_; grep { ($_->head->get('Content-Type')||"") =~ /$content_type/i } ($s +elf->parts) }; sub is_bounce { my ($self) = @_; my @parts; my @reasons; push @reasons, "mailer daemon" if $self->from =~ $sender; my $i = 0; while ($i < @headers) { my ($header,$r) = (@headers[$i,$i+1]); my ($content,$reason) = @$r; my @h = $self->head->get($header); for my $line (@h) { push @reasons, $reason if ($line =~ $content); }; $i += 2; }; if ($self->is_mime) { @parts = $self->parts; foreach my $part ($self->__mime_parts('message/delivery-status')) +{ push @reasons, __scan_delivery_report($part) }; }; if (@reasons) { @reasons = grep { defined $_ } @reasons; @reasons = "unknown" unless @reasons; }; @reasons; }; =head2 C<< $message->original_message_id >> This tries to find the original message id for a bounced message. C<< is_bounce >> should be true before you ask for the original message id. It returns a list of candidates for the original message id. =cut sub original_message_id { my ($self) = @_; my %result; if ($self->is_mime) { # let's hope for the original message in # the 'message/rfc822' part foreach my $part ($self->__mime_parts('message/rfc822'),$self->__m +ime_parts('text/rfc822-headers')) { for (map { /^Message-Id: (.*)$/i ? $1 : () } @{ $part->body }) { $result{$_} = 1 }; }; } else { for (map { /^Message-Id: (.*)$/i ? $1 : () } @{ $self->body }) { $result{$_} = 1 }; }; keys %result; };

In the module, I consider stuff a bounce that:

The method of collecting and checking the headers seems clumsy and inflexible to me, but except for InterScan NT virus bounces, it has proven to be effective for the kinds of bounces I get. If anyone has a more elegant method, it is welcomed.

Possibly, some SpamAssassin rules could also do the same for me, but I haven't gotten around installing and using it - I'm still using my hand-made mail sorting script as my first line of defense.


In reply to Detecting bounced mails by Corion

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.