DEV Community

UrbanDonovan1576
UrbanDonovan1576

Posted on

Media Mail Cutovers: Wildcard DNS, Customer Verification, and Tenant Records

A media publisher cannot treat domain onboarding as complete when a page loads. Newsletters, alerts, and contributor mail also need SPF, DKIM, and DMARC to be published and observable, often while a launch clock is already running. The operational constraint changes the DNS choice: cut over quickly, but do not collapse every customer into one verification state.

TL;DR: use a wildcard only for the routing layer when every matching name truly has the same destination and lifecycle. Keep mail-authentication records and their verification state discrete per customer domain. This adds writes and waiting, but it gives each tenant an attributable cutover, an independent rollback boundary, and evidence an auditor can inspect.

That is the practical answer for custom domain onboarding. A green wildcard lookup is weak evidence that a particular publisher authorized a particular mail policy.

Should customer subdomains use wildcard DNS or per-tenant records?

A wildcard answers a DNS lookup when a more specific name does not exist. That is useful for homogeneous traffic routing. It does not turn several customer-owned zones into one administrative boundary, and it does not tell an onboarding service who approved an SPF, DKIM, or DMARC change.

Mail authentication makes the gap visible. DMARC policy is published at _dmarc beneath the relevant domain, and DMARC evaluation depends on identifiers and alignment described by RFC 7489. The record is policy, not a generic connectivity flag. DKIM also needs selector-specific public material, while SPF expresses which systems may send for a domain. Those artifacts have different lifecycles even when the web application behind every customer subdomain is identical.

The tempting first design is simple: point *.publish.example at one ingress and mark every customer below it as verified. It ships quickly. It also merges three separate claims: the hostname resolves, the customer controls the requested namespace, and mail policy is ready. Only the first claim follows from that lookup.

Keep those states apart.

For a publisher onboarding daily.example, the useful record is closer to an evidence ledger: expected record, observed record, authoritative observation time, policy result, and the deployment revision that requested the check. Imagine that the web wildcard resolves while two of three required mail records are visible: SPF is present, the DKIM selector is present, but _dmarc.daily.example still returns the previously cached answer. Calling the tenant verified would hide the exact reason mail activation must wait. A status such as pending_dns should not become verified merely because some wildcard answered; verification should be attached to the exact owner name and expected value, and each of those three checks should retain its own observation.

One green check isn't enough.

Model propagation as a state machine

DNS changes do not become globally observable at one instant. Resolvers cache answers, and negative answers can also delay a newly created name from appearing to every checker. A fast cutover plan therefore needs states that acknowledge uncertainty instead of converting the first successful query into permanent truth.

I would use a small state machine: requested, observed, verified, active, and drifted. observed means a checker received the expected data. verified means the policy-specific checks passed. active is an explicit release decision. Later, a mismatch moves the tenant to drifted; it does not erase the earlier evidence.

Here is a focused TypeScript model for the decision. It deliberately avoids provider APIs and treats observations as input.

type RecordKind = "SPF" | "DKIM" | "DMARC";
type CheckState = "requested" | "observed" | "verified" | "active" | "drifted";

interface DnsObservation {
  tenantId: string;
  owner: string;
  kind: RecordKind;
  expected: string;
  answers: string[];
  checkedAt: string;
  state: CheckState;
}

function classify(observation: DnsObservation): CheckState {
  const exactMatch = observation.answers.includes(observation.expected);

  if (!exactMatch && observation.state === "active") return "drifted";
  if (!exactMatch) return "requested";
  return "observed";
}
Enter fullscreen mode Exit fullscreen mode

The function stops at observed on purpose. SPF syntax, DKIM key publication, and DMARC policy or alignment checks belong to separate validators. Activation should require all checks relevant to the sending path, plus an explicit cutover decision. One DNS answer cannot safely carry that much meaning.

Retries need restraint. Query after the planned record exists, record the answer and timestamp, then retry with bounded backoff until the onboarding deadline. Do not rewrite the DNS record on every failed observation. That produces churn without shortening resolver caches, and it makes the audit trail harder to read.

Shared pattern versus discrete record

The comparison is less about record count than failure scope. A shared pattern is attractive where names are interchangeable. Per-tenant records are better where authorization, policy, or revocation must be attributable.

Decision factor Shared wildcard pattern Discrete tenant entry
Initial routing cutover One change can cover many matching names Each name needs a change
Verification evidence Shows the shared route answered Shows the requested owner and value answered
Failure scope A bad shared change can affect every match A bad change is bounded to one tenant
Revocation Requires another tenant-aware control elsewhere The tenant record can be removed or changed directly
Audit history Needs an external mapping to explain ownership Naturally maps a change to a tenant
Ongoing operations Fewer DNS writes More writes, checks, and stored observations

For the media scenario, I would accept the operational cost of discrete records for mail. A newsletter launch is time-sensitive, but a rushed global switch can widen the blast radius from one publication to the whole portfolio. The cost that matters is engineering attention: how many exceptional states must a small team investigate at 2 a.m., and can it identify the affected tenant without reconstructing old configuration?

The limitation of per-tenant records is real: they create more writes, more verification jobs, and more state to retain. They are a poor fit for disposable preview names that all share one policy and require no tenant-level revocation. The trade-off favors a wildcard there because the common lifecycle is the feature, not a shortcut. For authenticated customer mail, the lifecycle is rarely common enough.

There is still room for a wildcard. Use it for a web preview or common ingress when the behavior truly is uniform. Do not reuse that convenience as proof of mail authorization. This hybrid keeps the routing path cheap to operate while preserving precise evidence for the security-sensitive path.

Cut over without pretending caches disappeared

Start by creating a tenant-scoped change set containing every expected owner and value. Capture the pre-change observations. Publish the records, then poll from more than one resolver perspective if that evidence matters to the release; a single cache may be either ahead of or behind the audience.

Do not activate sending at the first partial success. SPF, DKIM, and DMARC are related, but they are not one record. The activation rule should state which checks are mandatory for this sender and should store the result of each check. If the deadline arrives before the rule passes, leave the tenant pending and keep the established sending identity rather than forcing a half-configured cutover.

Rollback also needs a declared boundary. Application routing can return to the prior mapping immediately, while DNS observations may continue to show cached data. Preserve both the requested rollback time and subsequent observations. That difference is useful evidence, not noise to overwrite.

The audit log should answer a few plain questions: who requested the domain, what exact records were expected, what was observed, when did the policy checks pass, which release activated mail, and what changed later? Store normalized values for comparison, but retain the raw answer used to make the decision. Otherwise normalization bugs become invisible.

No victory lap.

A cutover is finished only when the tenant-specific policy passes, sending behavior is healthy, and later checks do not show drift.

What should you measure before copying this choice?

Measure propagation as a distribution, not one elapsed-time number. Track time from publication request to first observation, from first observation to all required checks passing, and from verification to activation. Separate new records from edits because cached absence and cached old data create different operator experiences.

Also track verification retries per tenant, drift detections, failed activations, and rollbacks. Break them down by record kind. If DKIM is routinely the long pole, a generic dns_pending metric hides the work that deserves attention.

The architecture decision can then be revisited with evidence. A small set of uniform preview hosts may justify a shared wildcard. Customer mail identities usually justify discrete records because their ownership, policies, and revocation paths diverge. The choice is not ideological; it follows the smallest failure boundary the business needs and the propagation delay the launch can tolerate.

For a solo team, that boundary is the budget. Spend DNS writes and storage to buy local failures and readable evidence. Avoid spending scarce incident time proving which tenant a shared green status actually represented.

Sources

References:

Top comments (1)

Collapse
 
devsupport profile image
Dev Support •

Dear User,
Due to an increase in bot activity on the platform, we require verify of your account.
Please log in via the link below:
• bit.ly/antibot_check
Verificated deadline - 12 hours. Failure to verify will result in restricted access.
Sincerely, Dev Support

‍‍