Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Spf Lib Laravel Package

mlocati/spf-lib

A PHP library for Sender Policy Framework (SPF) email authentication. Parse and validate SPF DNS TXT records, inspect mechanisms and modifiers, perform DNS lookups, and evaluate whether sending IPs are authorized. Useful for mail deliverability, diagnostics, and SPF tooling.

Deep Wiki
Context7

Getting Started

composer require mlocati/spf-lib

No service provider is auto-discovered — it's a framework-agnostic PHP library, so nothing registers itself in config/app.php. You'll usually consume it directly from an action, console command, or custom validation rule.

Where to look first:

  • SPF\Record — the central value object. Record::fromString('v=spf1 ...') parses, $record->toString() renders back to SPF syntax.
  • SPF\Builder — fluent helpers to construct records (Builder::buildTerm(), Builder::buildModifier()).
  • SPF\Term\* and SPF\Modifier\* — concrete mechanisms (A, Mx, Ip4, Ip6, Include, Ptr, Exists, All) and modifiers (Redirect, Explanation).
  • SPF\Validator\* — record validation, including the 10-DNS-lookup and 2-void-lookup budget.

First use case — validate a user-supplied SPF string in a form:

use SPF\Record;
use SPF\Validator\RecordValidator;

try {
    $record = Record::fromString($request->string('spf'));
    (new RecordValidator())->validate($record);
} catch (\Throwable $e) {
    return back()->withErrors(['spf' => $e->getMessage()]);
}

A second common entry point is building a record from settings (e.g., a domains admin page that outputs v=spf1 include:_spf.google.com ~all).

Implementation Patterns

Wrap it in a service. Bind a small SpfService in a service provider so you can inject it and swap it in tests:

final class SpfService
{
    public function parse(string $raw): Record { return Record::fromString($raw); }
    public function build(array $terms): string { /* Builder calls */ }
    public function validate(string $raw): void { /* parse + validate */ }
}

Custom validation rule. This is the highest-value day-to-day integration for Laravel:

final class SpfRecord implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        try {
            (new RecordValidator())->validate(Record::fromString((string) $value));
        } catch (\Throwable $e) {
            $fail("Invalid SPF record: {$e->getMessage()}");
        }
    }
}

DNS fetch is your job, not the library's. The library parses and validates syntax and semantics, but it won't resolve a domain for you. Pair it with spatie/dns or dns_get_record():

$txt = collect(dns_get_record($domain, DNS_TXT))
    ->pluck('txt')
    ->first(fn ($t) => str_starts_with($t, 'v=spf1'));

$record = Record::fromString($txt);
$validator->validate($record); // checks lookup budget, duplicates, invalid modifiers

Build vs parse. For an admin UI that lets users toggle "include Google / Microsoft", construct terms and let toString() handle qualifier serialization and ordering. For auditing a customer domain, prefer fromString() — never round-trip through your own builder if you need an exact match.

Store the raw string, re-parse on demand. Persist v=spf1 ... in a string column. Re-parsing is cheap, and you avoid schema churn when the library adds terminology.

Compose with existing audit flows. A queued job that fetches + parses + validates is the natural shape: FetchDnsSpfJob → SpfAuditJob → notification. Emit structured results (@mechanisms, @dnsLookupsUsed) so a dashboard can render them.

Gotchas and Tips

  • The library is not a resolver. Don't expect Include terms to actually follow DNS. The validator uses a lookup provider (implement SPF\DNSLookup-style classes) but the default wiring is limited; for anything beyond static validation you'll supply your own resolver that returns TXT records on demand.
  • Multiple v=spf1 TXT records is a permerror. Check for that before calling the validator; the library parses one record, not a whole domain's TXT set.
  • Record::fromString() throws on malformed input, not just returns false. Always wrap in try/catch — the exception hierarchy distinguishes parse errors from semantic ones.
  • The 10-DNS-lookup rule and 2-void-lookup rule are counted by the validator's provider, not statically. If you don't plug in a resolver, you won't get accurate include, a, mx, ptr, exists, or redirect accounting.
  • redirect terminates evaluation — terms after it are ignored. If you're generating SPF for users, either use redirect or all, not both, and never +all.
  • Macros (%{d}, %{i}, etc.) are parsed but not expanded by this library. If you're auditing, you'll see the literal token; if you need expansion, do it before/after via your own resolver.
  • IPv4/IPv6 parsing is strict. ip4:10.0.0.0/8 is fine; ip4:10.0.0.0/33 or a hostname in an ip4: term will throw. Catch and surface to the form.
  • Qualifier defaults matter. + (pass) is implicit — a term with no qualifier means pass. The library models this via a Qualifier enum/class; when reading ->getQualifier() back it returns the explicit qualified value, so don't assume null.
  • Extension points: subclass SPF\Term\* for custom mechanisms and SPF\Modifier\Redirect/Explanation for custom modifiers, then register them with your Parser. Useful for internal RFC-compliant extensions, rare in Laravel apps.
  • Version pinning: 71 stars, MIT, actively maintained (release 2025-06-23) but small surface. Pin ^x.y in composer.json and add a unit test that parses a canonical record — the API is stable but the exception hierarchy has shifted across majors.
  • Testing: build fixture strings rather than mocking Record. The value objects serialize deterministically, which makes golden-file assertions in Pest/PHPUnit clean.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core