Author image Steffen Ullrich


Mail::SPF::Iterator - iterative SPF lookup


    use Net::DNS;
    use Mail::SPF::Iterator;
    use Mail::SPF::Iterator Debug =>1; # enable debugging
    my $spf = Mail::SPF::Iterator->new(
        $ip,       # IP4|IP6 of client
        $mailfrom, # from MAIL FROM:
        $helo,     # from HELO|EHLO
        $myname,   # optional: my hostname
            default_spf => 'mx/24 ?all', # in case no record was found in DNS
            pass_all => SPF_SoftFail,    # treat records like '+all' as error
            # rfc4408 => 1,              # for compatibility only

    # could be other resolvers too
    my $resolver = Net::DNS::Resolver->new;

    ### with nonblocking, but still in loop
    ### (callbacks are preferred with non-blocking)
    my ($result,@ans) = $spf->next; # initial query
    while ( ! $result ) {
        my @query = @ans;
        die "no queries" if ! @query;
        for my $q (@query) {
            # resolve query
            my $socket = $resolver->bgsend( $q );
            ... wait...
            my $answer = $resolver->bgread($socket);
            ($result,@ans) = $spf->next(
                $answer                             # valid answer
                || [ $q, $resolver->errorstring ]   # or DNS problem
            last if $result; # got final result
            last if @ans;    # got more DNS queries

    ### OR with blocking:
    ### ($result,@ans) = $spf->lookup_blocking( undef,$resolver );

    ### print mailheader
    print "Received-SPF: ".$spf->mailheader;

    # $result = Fail|Pass|...
    # $ans[0] = comment for Received-SPF
    # $ans[1] = %hash with infos for Received-SPF
    # $ans[2] = explanation in case of Fail


This module provides an iterative resolving of SPF records. Contrary to Mail::SPF, which does blocking DNS lookups, this module just returns the DNS queries and later expects the responses.

Lookup of the DNS records will be done outside of the module and can be done in a event driven way. It is also possible to do many parallel SPF checks in parallel without needing multiple threads or processes.

This module can also make use of SenderID records for checking the mfrom part, but it will prefer SPF. It will only use DNS TXT records for looking up SPF policies unless compatibility with RFC 4408 is explicitly enabled.

See RFC 7208 (old RFC 4408) for SPF and RFC 4406 for SenderID.


new( IP, MAILFROM, HELO, [ MYNAME ], [ \%OPT ] )

Construct a new Mail::SPF::Iterator object, which maintains the state between the steps of the iteration. For each new SPF check a new object has to be created.

IP is the IP if the client as string (IP4 or IP6).

MAILFROM is the user@domain part from the MAIL FROM handshake, e.g. '<','>' and any parameters removed. If only '<>' was given (like in bounces) the value is empty.

HELO is the string send within the HELO|EHLO dialog which should be a domain according to the RFC but often is not.

MYNAME is the name of the local host. It's only used if required by macros inside the SPF record.

OPT is used for additional arguments. Currently default_spf can be used to set a default SPF record in case no SPF/TXT records are returned from DNS (useful values are for example 'mx ?all' or 'mx/24 ?all'). rfc4408 can be set to true in case stricter compatibility is needed with RFC 4408 instead of RFC 7208, i.e. lookup of DNS SPF records, no limit on void DNS lookups etc. pass_all can be set to the expected outcome in case a SPF policy gets found, which would pass everything. Such policies are common used domains used by spammers.

Returns the new object.

next([ ANSWER ])

next will be initially called with no arguments to get initial DNS queries and then will be called with the DNS answers.

ANSWER is either a DNS packet with the response to a former query or [ QUERY, REASON ] on failures, where QUERY is the DNS packet containing the failed query and REASON the reason, why the query failed (like TIMEOUT).

If a final result was achieved it will return ( RESULT, COMMENT, HASH, EXPLAIN ). RESULT is the result, e.g. "Fail", "Pass",.... COMMENT is the comment for the Received-SPF header. HASH contains information about problem, mechanism for the Received-SPF header. EXPLAIN will be set to the explain string if RESULT is Fail.

The following fields are in HASH


The clients IP address


The helo string from the client


How the identity of the sender was given, i.e. either mailfrom or helo.


The sender, either based on the mail from in the SMTP dialog (with identity being mailfrom) or the HELO/EHLO.

If no final result was achieved yet it will either return (undef,@QUERIES) with a list of new queries to continue, ('') in case the ANSWER produced an error but got ignored, because there are other queries open, or () in case the ANSWER was ignored because it did not match any open queries.


Creates value for Received-SPF header based on the final answer from next(). Returns header as string (one line, no folding) or undef, if no final result was found. This creates only the value, not the 'Received-SPF' prefix.


Returns ( RESULT, COMMENT, HASH, EXPLAIN ) like the final next does or () if the final result wasn't found yet.

If the SPF record had an explain modifier, which needed DNS lookups to resolve this method might return the result (although with incomplete explain) before next does it.

explain_default ( [ EXPLAIN ] )

Sets default explanation string if EXPLAIN is given. If it's called as a class method the default explanation string for the class will be set, otherwise the default explanation string for the object.

Returns the current default explanation string for the object or if non given or if called as a class method the default explanation string for the class.

lookup_blocking ( [ TIMEOUT, RESOLVER ] )

Quick way to get the SPF status. This will simply call next until it gets a final result.

TIMEOUT limits the lookup time and defaults to 20. RESOLVER is a Net::DNS::Resolver object (or similar) and defaults to Net::DNS::Resolver->new. Returns ( RESULT, COMMENT, HASH ) like the final next does.

This is not the preferred way to use this module, because it's blocking, so no lookups can be done in parallel in a single process/thread.


For convenience the constants SPF_TempError, SPF_PermError, SPF_Pass, SPF_Fail, SPF_SoftFail, SPF_Neutral, SPF_None are by default exported, which have the values "TempError", "PermError" ...

Arguments to use/import

The SPF_* symbols are available for import and are exported if no arguments are given to use or import. Same effect with adding :DEFAULT as an argument. Additionally the following arguments are supported:

DebugFunc => \&coderef

Sets a custom debug function, which just takes on argument. If given it will be called on all debug messages when debugging is active. This function takes as the only argument the debug message.

Debug => 1|0

Switches debugging on/off.


Steffen Ullrich <>


Copyright by Steffen Ullrich.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.