← Documentation Index


Mailmunge::Filter::Compat - base class for Mailmunge filtering that includes some backward-compatibility for migrating MIMEDefang filter code.


Mailmunge::Filter::Compat is a subclass of Mailmunge::Filter. As such, all methods documented in Mailmunge::Filter are also available here.

Mailmunge::Filter::Compat implements a filter_message function that in turn calls filter_begin, filter, filter_multipart, filter_end and filter_wrapup methods. These methods operate similarly to their counterparts in MIMEDefang filtering code and make it easier to migrate MIMEDefang filters to Mailmunge.

If you derive your filter from Mailmunge::Filter::Compat, you must not override filter_message. Instead, override filter_begin, filter, filter_multipart, filter_end and filter_wrapup as required.

Any functions that are callable from filter_message as well as Mailmunge::Context methods available to filter_message are available in filter_begin, filter, flter_multipart and filter_end.

The body filtering functions are called as follows:

  1. filter_begin is called once.

  2. Recursing through the MIME::Entity object $ctx->mime_entity, filter_multipart is called for each multipart/* sub-part and filter is called for each non-multipart sub-part.

  3. filter_end is called once. This is the last point at which you are allowed to modify the message body.

  4. filter_wrapup is called once. In filter_wrapup, modifications to the message body are not allowed, but you are allowed to modify top-level headers. Typically, this is where you would do DKIM-signing.

Note that if any method rejects a message by calling action_bounce, action_discard or action_tempfail, then filtering is short-circuited and remaining callbacks are not called.


    package MyFilter;
    use base qw(Mailmunge::Filter::Compat);
    sub filter_begin {
        my ($self, $ctx) = @_;
        # ... etc



Overrides filter_message from the base Mailmunge::Filter class. Do not tamper with or override this method.

The $ctx fields available are documented in Mailmunge::Filter's filter_message documentation; these same fields are available in filter_begin, filter_multipart, filter, filter_end and filter_wrapup.


Called once at the beginning of filtering. See "filter_message" in Mailmunge::Filter for the list of $ctx fields available.

filter_multipart($ctx, $part, $fname, $extension, $type)

filter_multipart is called once for each multipart/* part in the message. $part is the sub-part being filtered and is a MIME::Entity. $fname is the best-guess at the filename associated with the part (if any); it is taken from the Content-Type.name or Content-Disposition.filename MIME fields. $ext is the filename extension including the leading dot associated with $fname, and $type is the MIME type of the part.

filter($ctx, $part, $fname, $extension, $type)

filter is called once for each non-multipart part in the message. The arguments are the same as filter_multipart.


filter_end is called once at the end of filtering. This is the last place you can modify the message (which you can do with action_add_entity or action_add_part).


This method may only be called in filter or filter_multipart. It causes the part to remain in the message. If no method that removes or modifies a part is called, then action_accept is implicitly the default.


This method may only be called in filter or filter_multipart. It causes the part (and if multipart, all sub-parts) to be silently removed from the message.

action_drop_with_warning($ctx, $warning)

This method may only be called in filter or filter_multipart. It causes the part (and if multipart, all sub-parts) to be removed from the message. Additionally, a warning message is added in a new text/plain part that is appended to the message.

action_accept_with_warning($ctx, $warning)

This method may only be called in filter or filter_multipart. It causes a warning message to be added in a new text/plain part that is appended to the message.

action_replace_with_warning($ctx, $warning)

This method may only be called in filter or filter_multipart. It causes the part to be removed from the message and replaced with a new text/plain part containing the $warning message.

action_add_entity($ctx, $entity [, $offset])

Causes a new MIME::Entity to be added to the message at offset $offset, which is the zero-based index in the top-level message at which to add the entity. If $offset is not supplied, the part is added to the end of the message.

action_add_part($ctx, $type, $encoding, $data, $fname, $disposition, $offset)

Creates a new MIME::Entity whose MIME type is $type, Content-Encoding is $encoding, Content-Disposition is $disposition, Content-Disposition.filename is $fname and contents are $data. Then calls action_add_entity with the new part and supplied $offset. This is really just a convenience function that builds the MIME::Entity for you.


Returns Mailmunge's best-guess at the filename associated with MIME::Entity $entity. Note that the decoded filename is returned, so any MIME encoding is parsed and decoded.


Conversion of a filter from MIMEDefang to Mailmunge can range from very mechanical to quite complicated, depending on the filter. This section is a MIMEDefang-to-Mailmunge conversion guide.

The Filter

A MIMEDefang filter is a fragment of a Perl program, whereas a Mailmunge filter is a complete Perl program.

To convert a MIMEDefang filter to Mailmunge, your Mailmunge filter should start something like this:

    package MyFilter;
    use strict;
    use warnings;

    use base qw(Mailmunge::Filter::Compat);

    my $filter = MyFilter->new();


Mailmunge callbacks are similar to MIMEDefang, but have different arguments. The Following table shows the correspondence.

 MIMEDefang                                  Mailmunge
 ==========                                  =========

 sub filter_initialize {                     sub initialize {
     # ...                                       my ($self) = @_;
 }                                               # ...

 sub filter_cleanup {                        sub cleanup {
     # ...                                       my ($self) = @_;
 }                                               # ...

 sub filter_relay {                          sub filter_relay {
     my ($ip, $name, $port,                      my ($self, $ctx) = @_;
         $my_ip, $my_port, $qid) = @_;           # ...
     # ...                                   }

 sub filter_helo {                           sub filter_helo {
     my ($ip, $name, $helo, $port,               my ($self, $ctx) = @_;
         $my_ip, $my_port, $qid) = @_;           # ...
     # ...                                   }

 sub filter_sender {                         sub filter_sender {
     my ($sender, $ip, $name, $helo) = @_;       my ($self, $ctx) = @_;
     # ...                                       # ...
 }                                           }

 sub filter_recipient {                      sub filter_recipient {
     my ($recip, $sender, $ip, $name,            my ($self, $ctx) = @_;
         $first_recip, $helo,                    # ...
         $mailer, $host, $addr) = @_;        }
         # ...

 sub filter_begin {                          sub filter_begin {
     my ($entity) = @_;                          my ($self, $ctx) = @_;
     # ...                                       # ... Entity is $ctx->mime_entity
 }                                           }

 sub filter {                                sub filter {
     my ($entity, $fname,                        my ($self, $ctx, $entity, $fname,
         $extension, $mime_type) = @_;               $extension, $mime_type) = @_;
     # ...                                       # ...
 }                                           }

 sub filter_multipart {                      sub filter_multipart {
     my ($entity, $fname,                        my ($self, $ctx, $entity, $fname,
         $extension, $mime_type) = @_;               $extension, $mime_type) = @_;
     # ...                                       # ...
 }                                           }

 sub filter_end {                            sub filter_end {
     my ($entity) = @_;                          my ($self, $ctx) = @_;
     # ...                                       # ... Entity is $ctx->mime_entity
 }                                           }

 sub filter_wrapup {                         sub filter_wrapup {
     my ($entity) = @_;                          my ($self, $ctx) = @_;
     # ...                                       # ... Entity is $ctx->mime_entity
 }                                           }

 sub filter_map {                            sub filter_map {
     my ($map, $key) = @_;                       my ($self, $map, $key) = @_;
     # ...                                       # ...
 }                                           }

 sub filter_tick {                           sub tick {
     my ($tick_no) = @_;                         my ($self, $tick_no) = @_;
     # ...                                       # ...
 }                                           }

 sub filter_validate { ... }                 No equivalent in Mailmunge

 sub defang_warning { ... }                  No equivalent in Mailmunge

Return Values

Many MIMEDefang functions return an array of elements. In Mailmunge, they instead return a Mailmunge::Response object.

 MIMEDefang                                        Mailmunge
 ==========                                        =========

 return ('CONTINUE', 'ok');                        return Mailmunge::Response->CONTINUE();

 return ('TEMPFAIL', 'Message', 421, '4.1.1');     return Mailmunge::Response->TEMPFAIL(message => 'Message', code => 421, dsn => '4.1.1');

 return ('TEMPFAIL', 'Message', 571, '5.2.1');     return Mailmunge::Response->REJECT(message => 'Message', code => 571, dsn => '5.2.1');

 return ('DISCARD', 'Message');                    return Mailmunge::Response->DISCARD(message => 'Message');

 return ('ACCEPT_AND_NO_MORE_FILTERING', 'ok');    return Mailmunge::Response->ACCEPT_AND_NO_MORE_FILTERING();

Global Variables

MIMEDefang filters make use of a plethora of global variables. Mailmunge does not use any global variables. The correspondences for the most important variables are shown below.

 MIMEDefang                                        Mailmunge
 ==========                                        =========

 $MessageID                                        $ctx->message_id
 $RealRelayAddr                                    $ctx->connecting_ip
 $RealRelayHostname                                $ctx->connecting_name
 $CWD                                              $ctx->cwd
 @ESMTPArgs                                        @{$ctx->esmtp_args}
 @SenderESMTPArgs                                  @{$ctx->esmtp_args}
 $Helo                                             $ctx->helo
 $RelayAddr                                        $ctx->hostip
 $RelayHostname                                    $ctx->hostname
 $MIMEDefangID                                     $ctx->mailmunge_id
 $MessageID                                        $ctx->message_id
 $QueueID                                          $ctx->qid
 %RecipientESMTPArgs                               %{$ctx->recipient_esmtp_args}
 @Recipients                                       @{$ctx->recipients}
 $Sender                                           $ctx->sender
 $Subject                                          $ctx->subject
 $SubjectCount                                     $ctx->subject_count
 $SuspiciousCharsInHeaders                         $ctx->suspicious_chars_in_headers
 $SuspiciousCharsInBody                            $ctx->suspicious_chars_in_body
 $WasResent                                        $ctx->was_resent


Mailmunge moves a lot of functionality out of the core filter into modules. Here is a rough correspondence between MIMEDefang and Mailmunge functionality. Note that in some cases, we recommend external CPAN modules that already have the required functionality; duplicating that effort within Mailmunge would not be efficient.

DNSBL lookups

Instead of the various relay_is_blacklisted functions, use Net::DNSBL::Client.


Instead of stream_by_recipient or stream_by_domain, use Mailmunge::Action::Stream.


Instead of all the *_contains_virus_* functions, use File::VirusScan.

Bogus MX Host Checks

Instead of md_get_bogus_mx_hosts, use Mailmunge::Test::GetMX.


Instead of append_text_boilerplate or append_html_boilerplate, use Mailmunge::Action::Boilerplate.


Instead of the various spam_assassin_* functions, use Mailmunge::Test::SpamAssassin.


Instead of rspamd_check, use Mailmunge::Test::Rspamd.

SMTP Call-forwards

Instead of md_check_against_smtp_server, use Mailmunge::Test::SMTPForward.


Dianne Skoll <dianne@skollsoft.com>


This code is licensed under the terms of the GNU General Public License, version 2.

Copyright © 2021 Skoll Software Consulting