Files
skills/docs/communication/emails/email-processing/src/spf-checker/spf-checker.md
2026-05-29 08:20:56 +01:00

2.5 KiB

spf-checker

(no submodules)

Purpose

Implements the SPF (RFC 7208) evaluation algorithm: parses the record, walks each mechanism in order, follows include: and redirect= with bounded depth and lookup limits, and matches the candidate IPv4 / IPv6 address against ip4: and ip6: CIDR ranges. The DNS resolver is fully injectable so the package can be embedded in browsers, edge workers, or sandboxed runtimes that need to route lookups through DoH or a service mesh.

Features

  • Parses every mechanism token (ip4, ip6, a, mx, include, all, exists, redirect) and their qualifier prefixes (+ - ~ ?).
  • Bounded recursion: MAX_INCLUDE_DEPTH = 10 and MAX_DNS_LOOKUPS = 10 to surface RFC 7208 §4.6.4 permerror conditions instead of hanging.
  • IPv4 matching with Number.parseInt + bitmasks; IPv6 matching by expanding :: shorthand and comparing bit prefixes.
  • Records email_processing.spf.result_total attributed by result for every outcome.

Usage: Basic

import { checkSpf } from "@suaveplan/email-processing/spf";

const out = await checkSpf(senderIp, fromDomain, async (d) => {
    const recs = await dns.resolveTxt(d);
    return recs.map((parts) => parts.join(""));
});

Usage: Advanced

import { checkSpf, parseSpfRecord } from "@suaveplan/email-processing/spf";

const recs = await dnsResolver(domain);
const v = recs.find((r) => r.startsWith("v=spf1"));
if (v) {
    const mechs = parseSpfRecord(v);
    if (mechs.some((m) => m.type === "all" && m.qualifier === "+")) {
        log.warn({ domain }, "open SPF policy (+all)");
    }
}
const out = await checkSpf(ip, domain, dnsResolver);

API Reference

  • parseSpfRecord(record): SpfMechanism[].
  • ipv4Matches(ip, cidr): boolean.
  • ipv6Matches(ip, cidr): boolean.
  • checkSpf(ip, domain, resolver, depth?, lookupCount?): Promise<SpfCheckResult>.

Implementation Notes

a / mx / exists mechanisms are recognised but treated as non-matches because resolving them requires additional DNS lookups the caller may not want to fund — most real-world SPF records lean on ip4: / ip6: / include: anyway. The lookupCount parameter is exposed so callers can compose checkSpf across multiple top-level domains in a single run without re-instantiating the budget. The function records the SPF outcome to the OTel counter before returning, including the permerror paths, so dashboards can distinguish "bad record" from "no record".