#email #milter #spam #spf

bin+lib spf-milter

Milter for SPF verification

2 releases

new 0.0.2 Oct 11, 2020
0.0.1 Aug 31, 2020

#98 in Email


2.5K SLoC

SPF Milter

This software is in the initial development stage. Feedback from early adopters is welcome, but note that this is under construction and actively being worked on. Production use is not yet recommended.

SPF Milter is a milter application that verifies email senders using the Sender Policy Framework (SPF) protocol. It can be integrated with milter-capable MTAs to check and enforce authorisation published as SPF policy in the DNS.

SPF Milter closely adheres to the rules and recommendations of the SPF specification, RFC 7208. By design, SPF verification proceeds in the recommended manner: The first, optional step is to verify a client’s HELO identity (ie, the domain name given with the SMTP HELO command). If this step is not done or is not conclusive, then the client’s MAIL FROM identity (ie, the reverse-path or envelope sender given with the SMTP MAIL command) is verified. In either case, verification produces a final SPF result. The milter may then take action, either by rejecting the message with an SMTP error reply, or by recording the result in the message header.

Within the constraints of the specification, SPF Milter exposes highly flexible configuration options. Configuration toggles and settings for the verification procedure, result handling, SMTP reply, header fields, and more, support a broad range of use cases. SPF Milter is capable of in-flight configuration reloading, so that no restart is necessary for configuration changes.

In terms of implementation, SPF Milter is essentially a configuration interface to an SPF verifier integrated in the milter protocol. The SPF implementation is provided by the viaspf library, and the DNS implementation is provided by the domain library. We believe these to be a solid foundation for SPF milter software.


(not yet published)

SPF Milter is a Rust project. It can be built and/or installed using Cargo in the usual way. For example, use the following command to install the latest version published on crates.io:

cargo install --locked spf-milter

As a milter, SPF Milter requires the libmilter C library to be available. For example, on Debian and Ubuntu one needs to install the libmilter-dev package.

If your distribution does not provide pkg-config metadata for libmilter, try using the provided milter.pc file: Run any Cargo command with the directory containing milter.pc added to the pkg-config path:

PKG_CONFIG_PATH=/path/to/dir cargo build

The domain library requires the OpenSSL library and development files. On Debian and Ubuntu, install the package libssl-dev.

The minimum supported Rust version is 1.45.0.


Once installed, SPF Milter can be invoked on the command-line as spf-milter. This program reads configuration parameters from the default configuration file /etc/spf-milter.conf. At a minimum, the mandatory socket parameter must be specified in that file. See the included spf-milter.conf for a sample configuration.

Running spf-milter causes SPF Milter to read the configuration and start the milter. Send a termination signal to the process or press Control-C to shut the milter down.

To set up SPF Milter as a system service, try using the provided spf-milter.service systemd service. Install this file in /etc/systemd/system, then start and enable the service.

SPF Milter logs status messages to syslog. By default, warnings and errors are logged, as well as the verification result for each verified identity.


SPF Milter is configured primarily by setting configuration parameters in the configuration file /etc/spf-milter.conf. Parameters come with sensible default settings, and SPF Milter can be used right away by specifying just the mandatory socket parameter.

The included man page spf-milter.conf(5) currently serves as the reference documentation. (Tip: You can view the man page without installing by giving the path: man ./spf-milter.conf.5)

Configuration can be reloaded during operation by sending the signal SIGUSR1 to the milter process. Refer to the man page for details.

Before integrating SPF Milter in your mail server setup, consult at least the documentation for the parameters presented in brief below.


The mandatory socket parameter controls where SPF Milter opens a listening socket for the connection from the MTA. This parameter accepts a socket specification of the form inet:port@host for a TCP socket, or unix:path for a UNIX domain socket. The socket specification is passed to the milter library as-is.

Verification procedure

The main configurable facet of the verification procedure is whether the HELO identity is verified or not: this is controlled with the boolean parameter verify_helo. Thus there are two possible verification procedures:

  • MAIL FROM only: verify MAIL FROM identity and treat result as final outcome
  • HELO and MAIL FROM: verify HELO identity; if the result is definitive, treat it as the final outcome; if it is not definitive then verify MAIL FROM identity and treat that result as final outcome

Which HELO identity results are definitive can be controlled with definitive_helo_results.

Negative authorisation results and error results may be rejected at the SMTP level. The selection of results can be controlled with reject_results.

SMTP reply

When a definitive result has been reached, and the result is one to be rejected then it will be rejected with a transient or permanent SMTP error reply.

The SMTP reply code and reply text, and the enhanced status code can be specified for each SPF result. For example, for the fail result, use parameters fail_reply_code, fail_status_code, and fail_reply_text.


The type of header field to be added can be configured using the header parameter. The values Received-SPF and Authentication-Results each select the header field of the same name. The default Received-SPF header records SPF verification parameters in full detail, whereas the Authentication-Results header only records the result and sender domain. Compare:

Received-SPF: pass (mail.gluet.ch: domain of
  users-return-122976-dbuergin=gluet.ch@spamassassin.apache.org has authorized
  host receiver=mail.gluet.ch; client-ip=;
  identity=mailfrom; mechanism="include:_spf.apache.org"
Authentication-Results: mail.gluet.ch; spf=pass

Use cases

To make the above information more practical, this section presents two common example use cases: ‘Standard’ SPF, and SPF as part of DMARC.

Standard SPF

The standard, RFC-compliant SPF verification use case is well covered by SPF Milter’s default configuration settings. Therefore, just setting the mandatory socket parameter and leaving all other parameters at their default is enough to configure a standard SPF verification use case.


socket = inet:3000@localhost

Let us do a brief walkthrough of the default behaviour. SPF Milter will first verify the HELO identity (verify_helo = yes). If the HELO identity cannot be evaluated to one of the definitive authorisation results – pass or fail –, next the MAIL FROM identity is verified. Which HELO verification result is treated as definitive is configurable through the parameter definitive_helo_results.

The final result from either identity will then be enforced as suggested in RFC 7208. That is, the results fail, temperror, and permerror will be rejected with an appropriate permanent or transient SMTP error reply, and for all other results a header line recording the result is added to the message. The specifics of rejection at the SMTP level can be adjusted using the parameter reject_results and a number of parameters for each SPF result.

By default, a header field of type Received-SPF is used. The parameter for configuring the header field type is named header.


Treat softfail as a failing result. A very strict setup may treat softfail with the same severity as a fail result. To implement this, the softfail result needs to be added to the definitive HELO results and also to the results that get rejected.


definitive_helo_results = pass, fail, softfail
reject_results = fail, softfail, temperror, permerror

Enable both header fields. SPF Milter supports both of the specified header fields, and one might want to have them both added to messages. The configuration is straightforward. Note that in the RFC there is cautionary advice that care must be taken to ensure that both headers convey the same result; of course, SPF Milter ensures that already.


header = Received-SPF, Authentication-Results

SPF as part of DMARC

SPF may also be used as part of a DMARC verification setup. Domain-based Message Authentication, Reporting, and Conformance (DMARC) is specified in RFC 7489.


socket = inet:3000@localhost
verify_helo = no
reject_results =
header = Authentication-Results

DMARC will use an SPF result as an input for its own validation procedure. As shown, a few adjustments are necessary for a minimal DMARC setup.

First, DMARC does not use HELO identity verification, therefore this step should be turned off by disabling verify_helo. Only the MAIL FROM identity is considered.

Second, one might want to delegate authorisation to DMARC, and not do any rejection during SPF verification. Though depending on requirements other approaches may make sense. The above configuration disables rejection with reject_results = (the empty set), thus always letting the milter add a header line to messages and ultimately delegate the authorisation decision to a subsequent DMARC component.

The final parameter header changes the header type from the default Received-SPF to Authentication-Results, since this is the header field that DMARC relies on.


Reject failing HELO identity early. When SPF is set up for DMARC, it is still possible to enable HELO identity verification. In this variant, we set up HELO identity verification, deferring all HELO results except the result fail. We then reject this result, but only for the HELO identity. The resulting behaviour is just like above, with the difference that a HELO identity which evaluates to fail is rejected right away, before the usual Authentication-Results header field is added.


verify_helo = yes
definitive_helo_results = fail
reject_helo_results = fail
reject_results =


Copyright © 2020 David Bürgin

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.


~183K SLoC