Build1 publisher3 min readPublished
Per-tenant SPF, DKIM and DMARC checks give each publisher domain its own rollback point
Publishers onboarding custom domains should confine wildcard DNS to routing and verify three mail records per tenant, a dev.to guide argues. That costs extra writes and waiting, and it gives every customer its own rollback boundary and audit trail.
The Engineer · Build desk

What happened
- The simple design points *.publish.example at one ingress and marks every customer below it verified, though the lookup proves only that the hostname resolves.
- In the post's example, SPF and the DKIM selector for daily.example are visible while _dmarc.daily.example still returns a previously cached answer.
- Each check keeps the expected and observed record, the authoritative observation time, the policy result and the deployment revision that asked for it.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- cost Each customer domain costs at least three stored observations, and its mail launch waits on whichever record is slowest to clear resolver caches.
- exposure With one wildcard check, a publisher can show verified while its DMARC name still serves a stale answer, and an auditor finds no record of who approved the policy.
- decision A drifted domain has to pass validation and get a fresh release decision before it sends again, while other customers stay active.
"A green wildcard lookup is weak evidence that a particular publisher authorized a particular mail policy," the post's author wrote [13]. The reason is in how a wildcard resolves. It answers a lookup only when no more specific name exists [4]. Answering for names that have no record of their own is an odd credential for vouching for a customer's mail policy. The property is useful for routing identical traffic to one ingress, but it cannot tell the onboarding service which customer approved an SPF, DKIM or DMARC change [4].
The three mail records are separate objects. DMARC policy sits at `_dmarc` beneath the sending domain, and its evaluation depends on the identifiers and alignment RFC 7489 describes [5]. DKIM needs public key material published under a specific selector. SPF lists the systems allowed to send for the domain [5]. According to the post, those artifacts have different lifecycles even when the web application behind every customer subdomain is identical [5].
The constraint that forces a state machine is caching. DNS changes do not become visible everywhere at once [9]. Resolvers cache answers, and cached negative answers can keep a newly created name hidden from some checkers [9]. The post answers this with a separate observed state. Observed means a checker received the expected data, and it claims nothing about policy [10]. On retries, the post says to query only after the planned record exists and to store each answer with its timestamp [18].
The TypeScript sketch is small, and I think it is well judged. It takes observations as input and calls no provider API [11]. The match is exact, `answers.includes(expected)`, so any difference in the returned string counts as a miss [11]. The function can return three of the five states: requested, observed and drifted [15]. Verified and active never come out of it. SPF syntax, DKIM key publication and DMARC alignment go to separate validators, and activation needs an explicit cutover decision on top of every check relevant to the sending path [12].
Recovery is handled correctly. A drifted tenant whose record reappears classifies as observed, so it has to pass the validators and get a new release decision before it sends again [16]. One branch deserves a second look. A tenant already in drifted that fails its next check comes back as requested, because the drift branch tests only for active [17]. The post says drift does not erase earlier evidence [10]. In this sketch that history has to live in the ledger rows, with the state field holding only the latest classification [17].
The cost is real. Each tenant carries at least three observations with their own timestamps, where the wildcard design stored one green check for everyone [14]. The post accepts more writes and more waiting in exchange for an attributable cutover per tenant, a rollback boundary that stops at that tenant, and evidence an auditor can inspect [3]. For separate publishers sending mail under their own domains, I would pay that cost. The post's own test for a wildcard, that every matching name shares one destination and one lifecycle, still holds for the routing layer [2].
What to watch
- Whether the classifier's drift branch is extended so a tenant stays in drifted across repeated failed checks.
- How the separate SPF, DKIM and DMARC validators decide alignment, since classify hands that work to them.
- Whether the retry guidance sets a cap or backoff for checks against a _dmarc name that resolvers are still caching.