Email authentication : SPF

In the previous post we described DNS lookup and the IPREV mechanisms. They were great SPAM killers but the rise of the Internet in the 2000s saw the emergence of increasingly complex email architectures. A new protocol became popular through an experimental RFC : the Send Policy Framework (SPF)

In 2014 the adoption of the RFC7208 definitely anchors SPF as a standard.

This is the second stage of your journey in the email authentication landscape.

What is SPF ?

Basically, the SPF framework specifies which servers are allowed to send mail from a specific domain name. If a spoofed server attempts to send an email, the receiving MTA checks the spoofer’s IP. As it will not be declared in the SPF framework of the original domain, the connection will be rejected.

That means that the SPF framework allows the Administrative Management Domains (ADMDs) to explicitly authorize hosts to use their domain in the “MAIL FROM” or “HELO” identities. The authorization list is published in the DNS records of the sender’s domain.

DNS Records

The type of a SPF record is TXT. There should be only one SPF record per domain.

Here is a basic SPF record example: “only servers in the range and MTA (MX) are authorized to send emails from my domain All other senders are considered unauthorized.”          TXT "v=spf1 +mx ip4: -all"Code language: JavaScript (javascript)

There is also a tilde version: ~all. It warns that other senders are not allowed, but must still be accepted. This “Soft Fail” statement was first introduced for testing purposes, but is now used by various hosting providers.

As a domain must not have multiple SPF records, multiple include must be declared into a unique DNS records.	      TXT "v=spf1 -all"Code language: JavaScript (javascript)

Please also notice that the total number of DNS lookups can’t exceed 10.

Great tools and articles about SPF compliancy can be found on various websites such as PowerDMARC or EasyDMARC.

SPF evaluation

HELO/EHLO versus MAIL FROM identity

The RFC7208 does not enforce a HELO/EHLO verification.

“It is RECOMMENDED that SPF verifiers not only check the “MAIL FROM” identity but also separately check the “HELO” identity […] Additionally, since SPF records published for “HELO” identities refer to a single host, when available, they are a very reliable source of host authorization status. Checking “HELO” before “MAIL FROM” is the RECOMMENDED sequence if both are checked.“

The RFC 5321 tends to normalize the HELO/EHLO arguments to represent the fully qualified domain name of the SMTP client. However the vSMTP SPF verifier is prepared for the identity to be an IP address literal or simply be malformed. In this case the “MAIL FROM” check occurs.

“SPF check can only be performed when the “HELO” string is a valid, multi-label domain name […] SPF verifiers must check the “MAIL FROM” identity if a “HELO” check either has not been performed or has not reached a definitive policy result.“

Note that RFC5321 allows the reverse-path to be null. In this case, the RFC7208 defines the “MAIL FROM” identity to be the mailbox composed of the local-part “postmaster” and the “HELO” identity.


The vSMTP SPF verifier implements results semantically equivalent to the RFC.

Result	     Description
none	     (a) no syntactically valid DNS domain name was extracted from the SMTP session that could be used as
             the one to be or (b) no SPF records were retrieved from the DNS.
neutral	     The ADMD has explicitly stated that it is not asserting whether the IP address is authorized.
pass	     The client is authorized to inject mail with the given identity.
fail	     The client is not authorized to use the domain in the given identity.
softfail     The host is probably not authorized but the ADMD has not published a stronger policy.
temperror    A transient (generally DNS) error while performing the check.
permerror    The domain’s published records (DNS) could not be correctly interpreted.
policy	     (NOT IMPLEMENTED) Code language: JavaScript (javascript)

None vs PermError

When a receiving MTA begins to perform SPF authentication on an email, it fetches all the DNS TXT records that begin with “v=spf1”. In case SPF is not configured for the sending domain, and no SPF record is found in the DNS, a None result is returned.

On the contrary, if multiple SPF records beginning with “v=spf1” are found to exist for the same domain, an SPF PermError result is returned.

Result headers

The RFC7208 recommends to store the SPF evaluation in a message header. Two options are available.

The “Received-SPF” header

This is the legacy header. It includes enough information to enable reconstruction of the SPF evaluation of the message.

Received-SPF: pass ( domain of
  designates as permitted sender); client-ip=;
  envelope-from="";;Code language: JavaScript (javascript)

Please notice that the Received-SPF header field should be considered as trace field and therefore :

  • It must appear above all other Received-SPF fields in the message.
  • It should be prepended to the existing header, above the Received: field that is generated by the SMTP receiver.

The “Authentication-Results” header

This header is described in RFC8601 : Message Header Field for Indicating Message Authentication Status. It is designed to relay the result itself and related output details of likely use to end users.

Authentication-Results:; spf=pass smtp.mailfrom=example.netCode language: HTTP (http)

Both headers are in common use. However, it is important to note that they were designed to serve slightly different purposes and therefore they should both be added.

vSMTP implementation

The vSMTP spf module describe all the required functions to handle SPF queries. To query a DNS server we have to build a DNS resolver. Let’s use the previous DNS resolvers we used in the lastest post.

const google_dns = dns::resolver(#{
    config: "google_tls",
});Code language: PHP (php)

A basic configuration.

fn on_helo(ctx) {

  // EHLO/HELO SPF check
  let helo_identity = spf::check_host(#{
    ip: ctx.client_ip,
    helo: ctx.helo,
    dns_resolver: global::dns_resolver
  // Deny and close the connection if SPF result is not "pass"    
  if helo_identity != "pass" {
    return status::deny(`550 5.7.23 ${ctx.sender} is not allowed to send mail from ${ctx.client_ip}`)
  }"helo", helo_identity);

fn on_mail_from(ctx) {

  // MAIL FROM SPF check
  let mail_from_identity = spf::check_host(#{
    ip: ctx.client_ip,
    helo: ctx.helo,
    mail_from: ctx.sender,
    dns_resolver: global::dns_resolver

  if mail_from_identity != "pass" {
    status::deny(`550 5.7.23 ${ctx.sender} is not allowed to send mail from ${ctx.client_ip}`)
  } else {"mail_from", mail_from_identity);

fn on_pre_queue(ctx) {

  // Add authentication header
  auth::add_header(ctx, #{
    auth_serv_id: "mydomain.tld" // The domain name of the authentication server
}Code language: JavaScript (javascript)

What’s next ?

With IPREV and SPF, we focused on sender verification. Now it’s time to ensure and verify email integrity. This is done via the DKIM protocol…

Stay tuned.