spf is a comprehensive solution for Sender Policy Framework (SPF) evaluation,
implementing RFC 7208. It checks
whether an SMTP client's IP address is authorized to send mail for the envelope
sender's domain. Use it as a library within your application, or deploy the
included spfd binary as a standalone SPF checking service. The project provides
the evaluator, DNS resolution, command-line tools, and deployment configuration.
Built for high-throughput, concurrent mail processing, spfd handles independent
connections in parallel, with bounded active checks, cancellation, and graceful
shutdown. Benchmarks measure both interfaces under simple
and mixed-policy workloads, with separate checks for DNS failures and recovery.
The evaluator supports all RFC 7208 mechanisms (all, ip4, ip6, a, mx,
include, exists, and ptr), redirects, macro expansion, and failure
explanations. IPv4 and IPv6 clients are supported. Context-aware evaluation
limits each check to ten DNS-causing terms and two void lookups, with a
20-second deadline shared across includes and redirects. An earlier caller
deadline takes precedence.
All 203 cases in the pinned RFC 7208 suite pass, alongside 14 applicable development cases. Injectable resolvers make application tests repeatable without network access; separate loopback DNS tests exercise the actual UDP/TCP resolver paths. The conformance guide records tested behavior, corpus adaptations, and backend limitations, including legal DNS labels that require the explicit-server resolver.
spfd check evaluates one sender and prints JSON. spfd serve runs a persistent
SPF policy service with structured result logs and configurable limits on
connections and concurrent evaluations.
Build from this repository with Go 1.27 or later:
go build -o spfd ./cmd/spfd
# Check a sender using your local recursive DNS cache.
./spfd check -dns 127.0.0.1:5335 -ip 192.0.2.1 -sender sender@example.com
# Start the policy service in monitor mode.
./spfd serve -dns 127.0.0.1:5335 -listen 127.0.0.1:10023Replace the example client and sender with real SMTP identities, and set -dns
to your recursive resolver. By default the service allows 64 active evaluations
and 256 connections; each evaluation retains its own DNS limits. See the
command guide for flags, timeouts, and shutdown behavior.
spfd currently supports the Postfix access-policy protocol over loopback TCP
or a Unix socket. Connect it to your mail transfer agent (MTA) through that
protocol; for Postfix, add the policy check after existing relay protection.
Monitor mode logs SPF results while leaving the mail decision to the MTA.
Add -enforce to reject SPF fail and temporarily defer SPF temperror. See the
Postfix integration instructions for
configuration and response behavior.
A gRPC API layer is planned to support integrations beyond the current policy protocol. It is not available yet.
The 16 September 2026 mixed-policy benchmark completed 13.9 million evaluations across 126 trials with zero result/action mismatches, using Unbound and an NSD test authority on a shared four-vCPU Ubuntu VM. Selected warm-cache medians were:
| Interface / workload | Concurrent workers | Checks/s | p99 latency upper bound |
|---|---|---|---|
| Library / uniform | 128 | 64,768 | 9.1 ms |
| Library / frequently repeated domains (Zipf) | 128 | 99,373 | 8.1 ms |
| Service / uniform | 64 | 40,629 | 6.9 ms |
| Service / frequently repeated domains (Zipf) | 64 | 57,115 | 5.3 ms |
A separate simple-policy fixture reached 232,517 library checks/s and 98,860 service requests/s with cached local DNS. These are controlled measurements, not production capacity guarantees: policy mix, cache misses, concurrency, and logging affect throughput and latency.
- Mixed-policy and resilience report: throughput, latency, overload recovery, DNS faults, and shutdown checks; reproduction guide.
- Cached-DNS performance report: simple/include policies, concurrency sweeps, and CPU profiles; reproduction guide.
- Stalwart mail-auth comparison: throughput, tail latency, and the effect of resolver response sharing.
- Rspamd benchmark configuration and wttw/spf Go benchmark harness: retained comparison artifacts; this repository does not include their result reports. The Go harness uses in-memory DNS fixtures.
Applications can use the same evaluator directly, with system DNS, a configured recursive server, or a custom context-aware resolver. Each call owns its evaluation state, so independent checks can run concurrently; a shared custom resolver must also be safe for concurrent use. Requires Go 1.27 or later:
go get github.com/zaccone/spfpackage main
import (
"context"
"fmt"
"net"
"github.com/zaccone/spf"
)
func main() {
resolver, err := spf.NewServerResolver("127.0.0.1:5335")
if err != nil {
panic(err)
}
result, explanation, err := spf.CheckHostWithOptions(
context.Background(),
net.ParseIP("192.0.2.1"),
"example.com",
"sender@example.com",
spf.Options{Resolver: resolver, HELO: "mail.example.com"},
)
fmt.Println(result, explanation, err)
}The call returns the SPF result, an optional explanation, and a diagnostic
error. Inspect result when deciding how to handle mail: SPF fail normally
has no Go error. An empty Options{} selects system DNS. See the
API reference and
runnable examples for resolver injection and cancellation.
go test -count=1 -timeout=60s ./...
go test -race -count=1 -timeout=60s ./...
go vet ./...The conformance corpus uses in-memory DNS fixtures; resolver integration tests use ephemeral loopback ports. Neither requires public DNS access. See the test case inventory and fixture documentation for coverage and regeneration.
Deploy spfd with your MTA and a local recursive DNS cache to provide SPF
checking from incoming SMTP requests through DNS evaluation and policy
responses. We recommend Unbound and provide a ready-to-use
configuration, but any caching DNS server will work.
The supplied Debian/Ubuntu recipe combines Postfix, spfd, and Unbound, with
systemd services and configuration for the stack.
Configure and validate your DNS cache first, then point spfd serve -dns at its
address and port and connect the MTA to 127.0.0.1:10023. Our Unbound profile
listens on 127.0.0.1:5335, enables DNSSEC validation and prefetch, respects DNS
TTLs, and does not serve expired records. Start in monitor mode, verify mail
flow and SPF results, then choose whether to enable enforcement.
The end-to-end deployment guide covers installation, configuration, acceptance checks, operation, and rollback. The MTA remains responsible for SMTP and delivery; DKIM/DMARC and broader spam filtering are separate from this SPF deployment.
Licensed under MIT. Issues and pull requests are welcome.