DEV Community

ThalynRift3485
ThalynRift3485

Posted on

Apex Records and WWW Support Explained: Customer Domains Without Routing Coupling

TL;DR: Prove control with a dedicated DNS token, then treat apex and www routing as separate activation paths. An apex A record points at addresses and therefore couples the customer to an address-level ingress contract. A www hostname can follow a hostname-level contract instead. Neither routing choice should be the ownership proof. For a developer tool, onboarding is easier to reason about when verification, routing, and certificate readiness have distinct states.

This distinction matters before onboarding completes. A customer may prove control now, route traffic later, or choose never to route the apex at all. If one green check tries to represent all three events, the UI lies and the SDK grows exception flags.

Should customer domains support apex records, www, or both?

Routing answers a delivery question: where should requests go? Ownership verification answers an authorization question: may this account attach the name? Those are different questions, with different lifetimes.

An apex A record is an address contract. The customer publishes one or more IP addresses at the zone apex, so an ingress address change becomes their DNS change. The operational benefit is directness. The cost is coupling: the platform must keep that address contract stable, and the customer must coordinate any move. This can be acceptable for a platform-owned zone, where one operator controls both DNS and ingress. It is a heavier promise for a customer-owned zone.

A www-only contract moves the application entry point to a subdomain. The documentation can describe that hostname independently from the apex and can make apex redirection a separate customer decision. The trade-off is visible: users may still type the bare domain, so somebody must own that behavior.

Silence isn't a design.

The verification record should carry an unpredictable, account-bound value at a documented name. Keep it after activation if continuous proof is part of the contract; otherwise document exactly when it may be removed. RFC 7489 offers a useful precedent for DNS-based policy discovery: records live at purpose-specific names, and discovery rules are explicit rather than inferred from unrelated traffic records. This article borrows that separation, not DMARC's mail-specific semantics.

The constraint that changes the design

The hard constraint is split control. In a customer-owned zone, the platform can observe DNS but cannot assume it controls the apex, the registrar, existing mail records, or another service already using the name. In a platform-owned zone, the same team may safely automate more of the sequence. Treating those cases as identical creates config bloat because every hidden assumption eventually becomes a checkbox.

I would document the contract as three independent facts:

State Evidence What it authorizes
ownership_verified Expected verification value is observable The account may claim the domain
route_ready The selected apex or www target is observable Traffic activation may proceed
certificate_ready The serving edge reports readiness HTTPS may be enabled

The table is deliberately boring.

Good.

A CLI can print these states, an SDK can type them, and support can ask for one failed state instead of decoding a generic pending value. The three-state split also prevents a subtle documentation error: telling a customer that a verified domain is live when no route was requested. If the customer later moves from www to the apex, only route_ready needs to return to pending. Ownership stays verified, while certificate readiness can follow the new route through its own transition. One model handles both customer-owned and platform-owned zones without pretending their operators have the same permissions.

Do not silently switch a customer from apex to www, or the reverse, during verification. Store the requested routing mode as data. The proof token should remain valid across that choice because ownership did not change.

The smallest working verifier

The verifier needs no knowledge of A records, redirects, certificates, or a commercial DNS API. It asks a resolver for TXT values and compares an exact token. The resolver boundary also makes deterministic tests cheap.

type TxtLookup = (name: string) => Promise<readonly (readonly string[])[]>;

type Verification =
  | { status: "verified"; checkedName: string }
  | { status: "pending"; checkedName: string };

export async function verifyDomain(
  domain: string,
  expectedToken: string,
  lookupTxt: TxtLookup,
): Promise<Verification> {
  const checkedName = `_domain-verify.${domain}`;
  const answers = await lookupTxt(checkedName);
  const values = answers.map((parts) => parts.join(""));

  return values.includes(expectedToken)
    ? { status: "verified", checkedName }
    : { status: "pending", checkedName };
}
Enter fullscreen mode Exit fullscreen mode

TXT answers can arrive as multiple character strings, which is why the injected lookup returns arrays of parts and the verifier joins each answer before comparison. Exact comparison matters. Substring matching turns a stale or malformed value into authorization.

The function also refuses to decide what pending means operationally. A caller can retry after its chosen interval, record the observation time, and expose the checked name and expected value without claiming that DNS changes have a universal completion time. Timeouts and retries belong at the job boundary, not inside a pure comparison function.

For tests, feed the function one split answer, one wrong token, and an empty response. Three fixtures cover the decision surface without a network call or a fake control plane.

What I would change at scale

At scale, I would keep the decision small and enrich the evidence around it. Record the resolver vantage point, query time, normalized answer set, and verification transition. Do not log the reusable token in general application logs. Return stable reason codes such as record_missing and value_mismatch to the CLI, while keeping resolver failures distinct from a valid negative answer.

There is a race worth naming. Verification may succeed and the record may later disappear. The product contract must choose between proof at claim time and continuous proof. Continuous checks detect drift but can suspend a correctly routed domain because of an accidental DNS edit. One-time checks reduce operational noise but make later control changes somebody else's problem. I would choose continuous checks only when the authorization model also defines a grace state and an unambiguous recovery path; the extra enforcement is otherwise hard to justify.

Benchmark the path users actually feel: creation of the challenge, first successful observation, and time until the state reaches every read surface. Do not publish a universal propagation promise from one resolver sample. Track percentiles internally by resolver vantage point and routing mode; that shows whether the delay sits in DNS observation or in the platform's own state pipeline.

Keep certificate work downstream. A verified token does not prove that the selected route reaches the intended ingress, and a reachable ingress does not by itself identify the account allowed to claim the name. Combining those checks may save one status field. It costs far more during diagnosis.

Choosing the public contract

For customer-owned zones, default documentation should make the least coupled path obvious: verify first, select apex A or www as an explicit routing mode, and show the exact consequences of each. Apex support is a real capability, but it is also a commitment to an address-level contract. www support narrows the platform contract, but requires a declared plan for the bare domain.

For platform-owned zones, address coupling is less risky because DNS and ingress changes can share one deployment process. Even there, separate verification state pays off when accounts, teams, or environments contend for the same name. Authorization should not depend on whichever route happens to answer first.

The decision rule is short. Choose apex A when serving the bare domain is required and the address contract can be operated for its full lifetime. Choose www when a hostname-level application boundary and independent apex policy are more valuable. In both cases, prove control with a dedicated record and expose the states separately. That is the version an SDK can explain without twelve optional flags.

References

Further reading

Top comments (0)