DEV Community

RhettFletcher9678
RhettFletcher9678

Posted on

Logistics DNS Cutovers: Separate Mail Reputation Before Routing Branded Hostnames

Short answer: use a separate registrable domain and DNS zone for each brand that must own its mail policy and reputation boundary; share hostnames under one zone only when the brands also share administrative ownership, incident impact, and mail policy. For a logistics product accepting customer domains, keep web routing separate from that decision: a customer's portal hostname can point at the service without moving the customer's mail records into the service's zone.

The decision is not really "one control panel or many." It is a choice about authority and blast radius. A low-TTL cutover can make a web change converge faster, but no TTL compensates for mixing unrelated brand identity, DMARC policy, and rollback ownership under a shared parent.

Move fast, but preserve the boundary.

Should multiple brands share a DNS zone when mail reputation matters?

Usually not. If northstar-logistics.example and parcel-lane.example are separate registrable domains, they are separate points in the DNS namespace and should be operated as separate zones. Putting both in the same provider account may simplify access and billing, but it does not turn them into one DNS zone. This distinction matters in reviews because provider dashboards often use "zone" as a product container while DNS uses the word for a portion of the namespace served by an authoritative system.

A different case is north.brand-platform.example and parcel.brand-platform.example. Those names can live inside the brand-platform.example zone. That layout is reasonable for preview sites, internal tools, or brands that are deliberately one operational entity. The catch is that the parent team now controls delegation, mistakes can affect several brands, and DMARC policy discovery may reach the organizational domain when a subdomain has no policy record of its own.

DNS placement alone does not grant clean mail reputation. DMARC evaluates identifier alignment and policy: the domain in the visible From field is compared with an authenticated SPF domain or a DKIM signing domain under relaxed or strict alignment rules. Receivers make the acceptance decision. A separate zone gives a brand independent control of its authentication records and policy, which is useful, but it isn't a promise that every receiver will treat reputation as a single, perfectly isolated score.

That last qualification is important. I'm not sure any architecture diagram can predict a receiver's private reputation model; delivery evidence from the actual mail stream is what resolves that uncertainty.

Use this decision table in the design review:

Condition Default boundary Reason
Independent legal or customer-facing brands send mail Registrable domain and zone per brand Policy, delegation, and incident ownership stay explicit
Several labels are one organization with one mail policy Shared parent can be acceptable Fewer delegations, with intentionally shared impact
Customer only maps a portal such as track.customer.example Customer-owned hostname pointing to the application edge Web routing changes without transferring mail authority
Brand requires an independent rollback or DNS team Zone per brand One team can act without editing a shared authority boundary
Temporary non-mail environments share an operator Shared zone with separate hostnames The operational coupling is deliberate and short-lived

This is a default, not a law. A zone per brand is not suitable when the names are disposable environments under one corporate identity and the added delegation, access reviews, DNSSEC work, and monitoring would create ownership nobody will maintain. Keep a shared zone in that case. Conversely, don't use a shared parent merely to save a few records when separate brand teams need independent emergency control.

Separate customer routing from mail identity

Consider a logistics platform exposing track.customer.example. The customer can publish a CNAME from that hostname to a stable application target such as customer-104.edge.example.net. The application validates control, maps the hostname to the correct tenant, obtains the required certificate, and only then marks the domain ready for traffic. None of those steps requires changing _dmarc.customer.example, the customer's MX records, or its DKIM selectors.

This separation prevents a common category error: the hostname serving a parcel-tracking page and the domain authenticating shipment email are related to the same brand, but they are different control planes. Web cutover asks where HTTP traffic lands. Mail authentication asks which identifiers align and what a receiver should do when authentication fails. Treating one DNS change as if it answers both questions makes rollback ambiguous.

The apex needs extra care. A CNAME record cannot coexist with other data at the same owner name, while a zone apex necessarily carries SOA and NS data. Use a subdomain for the portable CNAME pattern, or choose an apex mechanism whose behavior is explicitly supported and documented by the customer's authoritative DNS operator. Don't assume that a provider-specific flattening feature has standard CNAME semantics.

For mail, publish and verify the brand's SPF, DKIM, and DMARC data under the brand's own authority. RFC 7489 defines DMARC records as DNS TXT records under _dmarc, and it defines how policy can apply to subdomains. Keep reporting destinations and access ownership in the runbook as well; aggregate reports can expose authentication drift before a stricter policy turns that drift into rejected mail.

One boundary, one owner.

Build a cutover that can fail closed without trapping traffic

The primary tension is propagation delay versus cutover speed. TTL is a cache lifetime expressed in seconds, not a scheduled global switchover. Recursive resolvers may have cached the old positive answer, while a hostname queried before it existed may be subject to negative caching. As a result, a team can observe both old and new answers during a legitimate transition. That is not evidence by itself that the authoritative update failed.

Write the runbook as a state machine. First, create the tenant mapping and validate domain control while the existing application still serves traffic. Next, publish the destination record with a TTL selected for the change window. If the current TTL is long, reduce it far enough ahead that caches have an opportunity to age out before the cutover; changing a TTL at the same moment as the target cannot shorten the lifetime of copies already cached. Then verify the answer through multiple independent recursive resolvers and query the authoritative servers directly. Only after routing, certificate, and tenant checks agree should the customer-facing state become active.

Keep the old target healthy through the rollback window. This is the DNS equivalent of idempotency: repeated checks and repeated state transitions must not create duplicate tenant bindings, issue conflicting ownership tokens, or detach a hostname that has already reached the desired state. If verification fails, stop promotion. Do not stack another speculative DNS edit on top of the first one; restore the last known target, record the observation time and resolver, and wait for cache behavior to converge.

A compact Go verifier is enough for a runbook check when the contract is a CNAME plus a DMARC record. It uses the system resolver, so production automation should run it from more than one network and should pair it with an authoritative query from the team's DNS tooling.

package main

import (
    "fmt"
    "net"
    "os"
    "strings"
)

func canonical(name string) string {
    return strings.TrimSuffix(strings.ToLower(name), ".")
}

func main() {
    if len(os.Args) != 3 {
        fmt.Fprintln(os.Stderr, "usage: verify-domain hostname expected-cname")
        os.Exit(2)
    }

    hostname := canonical(os.Args[1])
    expected := canonical(os.Args[2])
    actual, err := net.LookupCNAME(hostname)
    if err != nil {
        fmt.Fprintf(os.Stderr, "CNAME lookup failed: %v\n", err)
        os.Exit(1)
    }
    if canonical(actual) != expected {
        fmt.Fprintf(os.Stderr, "CNAME mismatch: got %q, want %q\n", actual, expected)
        os.Exit(1)
    }

    labels := strings.SplitN(hostname, ".", 2)
    if len(labels) != 2 {
        fmt.Fprintln(os.Stderr, "hostname has no parent domain")
        os.Exit(1)
    }
    dmarcName := "_dmarc." + labels[1]
    txt, err := net.LookupTXT(dmarcName)
    if err != nil {
        fmt.Fprintf(os.Stderr, "DMARC lookup failed for %s: %v\n", dmarcName, err)
        os.Exit(1)
    }
    for _, record := range txt {
        if strings.HasPrefix(strings.ToUpper(strings.TrimSpace(record)), "V=DMARC1;") {
            fmt.Printf("verified %s -> %s; DMARC present at %s\n", hostname, expected, dmarcName)
            return
        }
    }

    fmt.Fprintf(os.Stderr, "no DMARC1 policy found at %s\n", dmarcName)
    os.Exit(1)
}
Enter fullscreen mode Exit fullscreen mode

The example intentionally checks a parent-domain DMARC location for a two-level test name; it is not a complete organizational-domain implementation. Real public suffix handling and explicit subdomain policies require a library and test cases appropriate to the domains you accept. That's a limitation worth keeping visible, because string-splitting foo.co.uk would infer the wrong policy location.

Verify the control plane, not just one DNS answer

A green lookup from a laptop is weak evidence. The acceptance check should cover authoritative data, cached recursive views, certificate readiness, the HTTP tenant response, and mail authentication as separate signals. Record the queried name, record type, answer, TTL, resolver, and timestamp. Without those fields, an incident timeline collapses into screenshots nobody can compare.

Use explicit gates:

  1. The ownership token is present and belongs to the expected tenant.
  2. Authoritative servers return the intended routing record and no stale value.
  3. More than one recursive vantage point sees an allowed old or new answer during the transition.
  4. TLS serves a certificate valid for the customer hostname.
  5. The HTTP request resolves to the correct tenant, including an unknown-path probe that cannot leak another brand.
  6. SPF, DKIM, and DMARC checks are evaluated independently for each sending brand; a web CNAME result never substitutes for them.
  7. Alerts distinguish NXDOMAIN, SERVFAIL, certificate failure, and wrong-tenant routing because their owners and rollback actions differ.

Be strict here. A cutover is complete when the declared checks pass, not when the DNS dashboard says the record was saved.

For observability, track the domain state as pending, verified, active, or rollback, and make transitions idempotent. A queue retry must not activate the same hostname twice or send duplicate customer notifications. That sounds like scheduling hygiene because it is: DNS propagation is asynchronous, and any controller polling it inherits the same retry and deduplication problems as a cron or queue worker.

Roll back by restoring authority, then learn from the timeline

Define rollback before lowering the TTL. The trigger might be a wrong-tenant response, certificate mismatch, or failure to meet the declared resolver convergence window. The action is to restore the previous routing target while leaving both application paths capable of serving the tenant until cached answers expire. Avoid deleting the new tenant mapping immediately; resolvers still holding the new answer need a valid destination during rollback.

Afterward, reconstruct the timeline from authoritative changes and resolver observations. Ask whether the pre-change TTL was reduced early enough, whether negative caching was considered, whether certificate readiness was incorrectly coupled to DNS publication, and whether one team had unambiguous authority to revert. These questions lead to changes a runbook can enforce.

The final architecture rule is plain: isolate independent brand mail authority, leave customer mail DNS with the customer, and make web-hostname activation a verified, reversible state transition. Shared zones still have a place for genuinely shared ownership. They are a poor shortcut for boundaries the incident response process already treats as separate.

References

Top comments (0)