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

Dmarc Record Builder Laravel Package

cbowofrivia/dmarc-record-builder

A PHP package for building DMARC TXT records programmatically. Configure policy, subdomain policy, alignment, reporting addresses and percentage, then generate a valid DMARC record string for your DNS zone.

View on GitHub
Deep Wiki
Context7

Getting Started

  • composer require cbowofrivia/dmarc-record-builder:^4.0 — the package is now dependency-free, so it adds no transitive packages and does not pin your Laravel version. Requirements: PHP 8.3+.
  • There is nothing to register: no service provider, no config to publish, no facade. It is a value-object library you use directly.
  • Look first at the record class itself: the create() factory / constructor arguments, the chainable tag methods, the static parse(), and Exceptions\InvalidDmarcRecordException.
  • First use case — generating the DNS TXT value for a tenant domain when they configure email auth:
use CbowOfRivia\DmarcRecordBuilder\DmarcRecord;

// Tag methods mirror the DMARC tag names; confirm exact signatures
// against the README before wiring this into a form.
$record = DmarcRecord::create()
    ->policy('quarantine')
    ->rua('mailto:[email protected]');

return $record->toString(); // "v=DMARC1; p=quarantine; rua=mailto:..."

Publish that string to a TXT at _dmarc.example.com via your DNS provider's API.

Implementation Patterns

Store structured state, render on demand. Don't make the TXT string the source of truth. Keep the individual settings in columns/JSON on your domains table and cast to the record whenever you need to emit DNS. This survives both provider changes and future package upgrades.

A cast for the model layer. Wrap hydration/serialisation in a custom cast so controllers never touch the builder:

public function getDmarcRecordAttribute(): DmarcRecord
{
    return json_decode($this->dmarc_config, true) === []
        ? DmarcRecord::create()
        : DmarcRecord::fromArray(json_decode($this->dmarc_config, true)); // or equivalent
}

Validate by constructing, then translate the exception. The builder is your validator for DMARC-specific rules; convert its exception into Laravel validation errors at the boundary:

try {
    $record = DmarcRecord::create()->policy($request->policy);
} catch (\InvalidArgumentException $e) {          // was Webmozart's class in v3
    throw ValidationException::withMessages(['policy' => $e->getMessage()]);
}

Parsing for audits and imports. When you ingest existing records from dns_get_record($domain, DNS_TXT), run them through parse() in an artisan command and diff against what the tenant has configured. This is the cheapest way to spot records that drifted from your UI.

Keep DNS I/O out of the builder's job. The package produces a string; publishing, TTLs, and the _dmarc subdomain naming are yours to own in a DmarcPublisher service. This keeps the package swappable.

Round-trip tests are trivial now. With zero runtime dependencies, unit-test the exact rendered string and asserted parse-back in a plain PHPUnit test — no Laravel bootstrapping needed.

Gotchas and Tips

  • pct() and interval() are gone in v4. RFC 9989 dropped pct and ri. Calls will now fatal with "undefined method"; the pct: / interval: constructor and create() arguments and the $pct / $interval properties are also removed. Grep your app for all five forms.
  • Silent data loss on re-cast. parse() still accepts legacy records containing pct and ri without error, but drops those tags when you re-render. If a tenant is on pct=25 and you round-trip their record through a settings save, they silently jump to 100%. Snapshot the raw TXT string before any re-cast if you need an audit trail.
  • Catch type changed. Validation now throws CbowOfRivia\DmarcRecordBuilder\Exceptions\InvalidDmarcRecordException, which extends \InvalidArgumentException. Existing catch (\InvalidArgumentException) blocks keep working; only code that named Webmozart\Assert\InvalidArgumentException will silently stop catching. Exception messages are unchanged, so old log greps still match.
  • PHP 8.3 floor. Bump your CI matrix, Forge/Vapor/Envoy images, and any Dockerfile that pins 8.2. Composer will refuse to install otherwise, which is the loud failure — but a stale CI matrix is the quiet one.
  • illuminate/collections is no longer a dependency. If you were relying on parse() handing back a Collection (calling ->map(), ->first() on the result), wrap it yourself with collect($record->parse(...)). Plain PHP arrays now.
  • Unknown tags are dropped, not rejected. Don't treat parse() as a strict linter for third-party records — an unsupported tag disappears quietly instead of raising. Validate values (policy strength, rua addresses) yourself if the input isn't yours.
  • Wrap, don't extend. Subclassing the record class couples you to its internals across major bumps. A thin App\Support\Dmarc wrapper containing every call to the package means the next breaking release touches exactly one file.
  • Debugging: print the rendered string and diff it against a known-good record from a working domain — the semicolon-separated format makes whitespace and ordering issues obvious.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings