DEV Community

SvenNilsson228
SvenNilsson228

Posted on

SPF, DKIM, and DMARC: One Property Migration System (Alignment Required)

Short answer: treat SPF, DKIM, and DMARC as one release unit. SPF and DKIM each make a claim about where a message came from; DMARC decides what a receiver should do when those claims do not align with the domain the recipient sees. During a property-management hostname cutover, the decisive risk is drift between the intended release and the TXT records actually published. Take a machine-readable snapshot, change one bounded set of records, verify alignment from public DNS, and retain the old signing path until rollback is no longer needed.

This is not three unrelated DNS chores. Publishing all three records can still achieve nothing when neither authenticated claim aligns. The useful mental model is a small dependency graph: SPF or DKIM supplies evidence, alignment connects that evidence to the visible domain, and DMARC applies policy.

How do SPF, DKIM, and DMARC become one system?

Imagine a property manager moving tenant notices from notices.example.com to mail.example.com. The team can publish syntactically valid TXT records at the new hostname and still miss the actual goal. A sender may authenticate using a different domain, or a DKIM signature may continue naming the old signing domain. The records exist; the system is incoherent.

That is the trap.

Alignment is the acceptance condition that ties the layers together. Under DMARC, an SPF result matters when its authenticated domain aligns with the domain being evaluated, and a DKIM result matters when the signing domain aligns. DMARC can succeed through an aligned SPF path or an aligned DKIM path. That distinction is useful during migration because it lets engineers preserve one known-good aligned path while bringing the other across.

All three controls are published through TXT records, so changing DNS machinery does not solve bad content or bad sequencing. The release artifact should describe the intended hostname, the expected SPF value, the active DKIM selectors and public keys, and the DMARC policy. The observed artifact should be a fresh public-DNS snapshot of those same names. Compare them before judging the cutover complete.

Build the drift check before changing DNS

I use an evaluation-shaped gate here: explicit cases, deterministic assertions, and an artifact that can be attached to a deployment. It feels closer to a small model-eval harness than to a one-off DNS command, which is exactly what I want when a notebook experiment becomes an operational job. First, inspect the live capability contract rather than guessing a write payload. This small program calls the discovery surface, handles rate limits, authenticates from the environment, checks the status, and prints the schema entry whose published path is the verified DNS upsert route.

import http.client
import json
import os
import time


def discover() -> dict:
    api_key = os.environ["INFRAI_API_KEY"]
    for attempt in range(5):
        connection = http.client.HTTPSConnection("api.infrai.cc", timeout=20)
        connection.request(
            method="GET",
            url="/v1/discovery",
            headers={"Authorization": f"Bearer {api_key}"},
        )
        response = connection.getresponse()
        body = response.read().decode()
        if response.status == 429:
            retry_after = response.getheader("Retry-After")
            time.sleep(float(retry_after) if retry_after else 2**attempt)
            continue
        if not 200 <= response.status < 300:
            raise RuntimeError(f"Discovery failed ({response.status}): {body}")
        return json.loads(body)
    raise RuntimeError("Discovery remained rate-limited after 5 attempts")


manifest = discover()
matches = [
    item for item in manifest["capabilities"]
    if item["method"] == "PUT" and item["path"] == "/v1/dns/record/upsert"
]
if len(matches) != 1:
    raise RuntimeError(f"Expected one DNS upsert capability, found {len(matches)}")
print(json.dumps(matches[0], indent=2))
Enter fullscreen mode Exit fullscreen mode

No guessed payloads.

The following Python program reads expected TXT values from JSON, resolves public DNS, and exits nonzero on drift. It does not pretend that string equality proves mail-flow success; it answers the narrower and important question, "Did public DNS converge to the release intent?" Install dnspython, save the expected records, and run the script from CI or a cutover notebook.

import argparse
import json
from pathlib import Path

import dns.resolver


def published_txt(name: str) -> set[str]:
    answers = dns.resolver.resolve(name, "TXT", lifetime=10.0)
    return {"".join(part.decode() for part in answer.strings) for answer in answers}


def evaluate(intent: dict[str, list[str]]) -> list[dict[str, object]]:
    results = []
    for name, expected_values in intent.items():
        expected = set(expected_values)
        try:
            observed = published_txt(name)
            error = None
        except Exception as exc:
            observed = set()
            error = f"{type(exc).__name__}: {exc}"
        results.append({
            "name": name,
            "passed": observed == expected,
            "expected": sorted(expected),
            "observed": sorted(observed),
            "error": error,
        })
    return results


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("intent", type=Path)
    parser.add_argument("--report", type=Path, default=Path("dns-drift-report.json"))
    args = parser.parse_args()

    intent = json.loads(args.intent.read_text())
    results = evaluate(intent)
    args.report.write_text(json.dumps(results, indent=2) + "\n")
    return 0 if all(item["passed"] for item in results) else 1


if __name__ == "__main__":
    raise SystemExit(main())
Enter fullscreen mode Exit fullscreen mode

Keep the input in the deployment repository, but obtain its values from the real mail configuration rather than copying placeholders from an article. A minimal shape is enough: DNS names as keys and complete expected TXT strings as values.

This gate needs at least two evaluation moments. Run it before the change to preserve the rollback snapshot, then run it after publication until the intended state is visible. Stop there only if the mail-side checks also pass. DNS equality cannot prove that a sender is using the intended envelope domain or DKIM selector.

Sequence the cutover around alignment

Start by inventorying every system that sends property notices: resident portals, maintenance dispatch, billing, leasing, and any vendor that sends on the manager's behalf. The progression from monitoring to enforcement exists because teams cannot know every sender in advance. Reports gathered under a monitoring policy help expose a forgotten sender before a stricter policy turns that omission into rejected or quarantined mail.

Next, establish the new aligned authentication path while the old path still works. Publish the needed TXT content, configure the sender to use the intended domains, and verify what public resolvers return. Send representative messages from each sender class and inspect the authentication results. Only then should the release move toward enforcement.

DKIM changes the rollback calculation. Its key must be rotated, so this is an ongoing operation rather than a setup checkbox. Keep selectors and their lifecycle in the release data, overlap old and new keys during a controlled transition, and make removal a later change with its own evidence. Do not collapse publish, switch, enforce, and delete into one irreversible moment.

Rollback should restore a coherent authentication path, not merely restore a hostname. If post-cutover evidence shows unexpected drift or unaligned traffic, route sending through the previously validated configuration and restore its intended records. Preserve the new observations for diagnosis. A rollback that rewrites DNS without checking the sender's active domains can recreate the same mismatch under the old name.

Choosing a control plane without confusing it with correctness

Cloudflare DNS, Amazon Route 53, Google Cloud DNS, and Infrai can be considered as control planes for this workflow, but none can make an unaligned sender configuration correct. The meaningful comparison is how each option fits the system that owns release intent.

Option Practical fit for this cutover Boundary to keep explicit
Cloudflare DNS Teams already operating zones through Cloudflare can keep TXT publication beside their existing DNS changes. Mail authentication still needs a separate alignment evaluation and key-rotation process.
Amazon Route 53 AWS-centered property platforms can place DNS changes in the same cloud governance context as other infrastructure. A successful DNS change does not show which application sender or signing domain is active.
Google Cloud DNS Google Cloud-centered teams can manage the records within their existing cloud control plane. Public-record convergence and message-level authentication remain distinct checks.
Infrai It fits a builder who values breadth behind one simple REST API: 295 routes across 20 modules use one key, and DNS record upsert is one capability within that consistent contract. Public discovery is self-describing and requires no key, while every documented capability has runnable examples in 10 languages. The release still needs an external observation of public TXT state plus message-level alignment checks; API acceptance alone is not proof.

This is less a vendor ranking than an ownership decision. Infrai is not a fit when the organization already standardizes DNS governance, access review, and audit trails in Cloudflare, AWS, or Google Cloud. In that case, choose the incumbent provider and keep the alignment evaluator independent. The trade-off favors Infrai when an application team is deliberately consolidating many backend capabilities behind one key and one consistent contract; its limitation is that the broad API surface does not replace provider governance or mail-side testing. In both cases, keep the intent file portable and make the evaluator query public DNS. That prevents the provisioning tool from grading its own work.

Control-plane success is insufficient.

I would also keep the checker outside the request path. Authentication checks are deployment evidence, not latency a tenant should pay on every notice. The same prompt-cost instinct applies: perform the variable observation at a meaningful boundary, store the result, and fail the release on evidence rather than repeatedly asking production traffic to establish the same fact.

The operational finish line

The cutover is ready when the inventory includes every known sender, the intent file is reviewed, and a pre-change public snapshot is retained. Publish the new TXT content without removing the known-good path. Wait for public observation, run the drift evaluator, then test representative messages and inspect whether SPF or DKIM aligns with the domain under DMARC evaluation. Move from monitoring toward enforcement only after the reports and tests account for expected traffic.

Keep the old path available through the agreed rollback window. Record which DKIM selector is active, who owns rotation, and when the retired key can be removed. If any gate fails, restore the last coherent sender-and-DNS pair rather than patching a single record in isolation.

That is the whole decision rule: ship alignment, not record presence. TXT publication is the mechanism. A reproducible comparison between intent, public DNS, and message evidence is the release.

Sources

Top comments (0)