SPF Why?

Explains your SPF checker results.


The sender is not authorized to send mail according to the published SPF record. Messages should be rejected by servers that enforce SPF.

SPF check by mx1.xel.nl for samy@ascha.org in scope mfrom has result fail because matches -all in SPF record:

v=spf1 mx include:spf.xel.nl -all

Use the form below if you want to do a manual SPF check:

This tool can be used to explain SPF check results by including a parameterized link in logs and error messages, or by manualy querying the API. It copies the simple API at open-spf.org/Why and can be used as an alternative in e.g. policyd-spf.

API Usage

There is one endpoint: /.

It accepts a GET request with the following query parameters:

Required Name Parameter Default Examples
Versions v 1 v=1
Scope s mfrom s=helo
* Identity id id=user@example.com, id=mta.example.com
* IP-address ip ip=, ip=fe80::2a:7
Receiver r unknown r=mx.example.com


GET /?id=user@example.net&ip=

HEAD Request

Alternatively, you can send a HEAD request where the result code, and other details for the check, will be in the response headers as:

X-SPF-Why-Result-Code: fail
X-SPF-Why-Result-Mechanism: -all
X-SPF-Why-Query-Versions: 1
X-SPF-Why-Query-Scope: mfrom
X-SPF-Why-Query-Id: user@example.net
X-SPF-Why-Query-Receiver: mx.example.com

Content Types

You can use other content types besides the default text/html. Specifically: application/json and text/plain.

Example JSON response:

  "query": {
    "versions": [
    "scope": "mfrom",
    "identity": "user@example.net",
    "ip": "",
    "receiver": "mx.example.com"
  "result": {
    "code": "fail",
    "mechanism": "-all"

Example plaintext response:

SPF check by mx.example.com
for user@example.net
in scope mfrom
has result fail
matches -all
in SPF record:
v=spf1 -all

This tool uses mlocati/spf-lib for SPF heavy lifting.

Hosting by xel.

Samy Ascha, samy@ascha.org.