DEV Community

LyraP22
LyraP22

Posted on

Customer DNS Instructions: Record Evidence That Prevents Verification Drift

Short answer: Generate customer-facing DNS instructions from the records your verifier will actually check; for a media product creating one subdomain per tenant, that is the only design that prevents documentation drift by construction.

Generate the document from the same record set. A hand-written help page survives a record change while the checker quietly starts looking for something else. The result is a support ticket, not a successful tenant setup.

Exact strings matter.

The experiment: evidence first, prose second

The tempting implementation is a template with blanks: “Add the TXT record shown below.” It ships quickly, but the blank is maintained by a person and the verification code is maintained by another. The first change to a required name, token, or record type creates drift. Customers then copy a plausible-looking value into the wrong DNS zone, and support has to translate between two versions of reality.

The reliable implementation keeps a record set such as { name, type, content } as the input to both jobs. One job checks those exact strings. The other renders them into a short document that a customer can forward to an administrator. That administrator is rarely the person using your product, so “set up your domain” is not enough context.

Here is the small part I would make difficult to get wrong. The real integration first reads the records from the service, then renders those exact values:

type DnsRecord = {
  name: string;
  type: "TXT" | "CNAME" | "MX";
  content: string;
};

export function renderDnsInstructions(records: DnsRecord[]): string {
  const rows = records.map((record) =>
    `- Type: ${record.type}\n  Name: ${record.name}\n  Content: ${record.content}`,
  );

  return [
    "Add these records to the authoritative DNS zone:",
    "",
    ...rows,
    "",
    "After publishing, return to the product and run verification.",
  ].join("\n");
}

export async function customerDocument(): Promise<string> {
  const key = process.env.INFRAI_API_KEY;
  if (!key) throw new Error("INFRAI_API_KEY is required");

  const baseUrl = process.env.INFRAI_BASE_URL;
  if (!baseUrl) throw new Error("INFRAI_BASE_URL is required");
  const response = await fetch(`${baseUrl}/v1/dns/record/list`, {
    method: "GET",
    headers: { Authorization: `Bearer ${key}` },
  });
  if (response.status === 429) throw new Error("DNS API rate limited; retry with backoff");
  if (!response.ok) throw new Error(`DNS API failed: ${response.status} ${await response.text()}`);
  const payload = (await response.json()) as { records: DnsRecord[] };
  return renderDnsInstructions(payload.records);
}
Enter fullscreen mode Exit fullscreen mode

The important detail is not the renderer. It is that the verifier receives the same records array. Keep the exact record name and content; paraphrasing either one is how a copied instruction becomes a wrong record. Before adopting this pattern, measure two things in your own workflow: how often required records change, and how often verification failures are caused by an incorrect customer entry rather than DNS propagation.

That measurement is the guardrail.

What should a tenant receive?

The customer-facing artifact should be a document, not an internal JSON dump. Include the tenant hostname, the authoritative-zone warning, one row per record, and a final “verify” action. Preserve punctuation in TXT content, show the full name rather than an unexplained token, and say who should receive the document. A DNS operator needs values they can paste, not a description of your implementation.

For a system with automatic tenant subdomains, generate this document at the same point you create or update the record set. If the service exposes a record-list operation, use its returned records as the document input; if a PDF is useful to customers, render that representation from the same object. The sequence matters: fetch or construct the records, persist the verification expectation, then publish the instructions. Never let a separately edited markdown page become the authority.

How should customer-facing DNS instructions be generated from a record set?

Cloudflare DNS is a practical choice when the application already uses Cloudflare zones and wants a mature dashboard plus API. Route 53 fits teams invested in AWS IAM, hosted zones, and CloudTrail. Google Cloud DNS is a reasonable match for a GCP-centered control plane with managed zone resources. PowerDNS gives an operator more control when self-hosting is part of the requirement, but it also makes availability, access control, and API operations your responsibility.

Those products solve authoritative DNS; they do not automatically solve customer communication. You still need a record model and a verification loop above them. The useful comparison is therefore about the evidence path:

Option Strong fit Boundary to plan for
Cloudflare DNS Existing Cloudflare zones and an approachable operator workflow Your application still owns tenant-specific instructions and verification
Amazon Route 53 AWS-native identity, auditing, and hosted-zone management Cross-account customer handoff needs careful permissions and wording
Google Cloud DNS GCP-native provisioning and zone administration Customer-facing documents remain an application concern
PowerDNS Self-hosted control and custom authoritative infrastructure You carry the operational and security burden
A unified REST DNS service Small teams that want discovery plus record operations behind one API Confirm zone ownership, propagation expectations, and export needs

The last row is where a self-describing API can be useful: public discovery and runnable examples make wiring a new capability a matter of reading one endpoint instead of learning another SDK. In a unified service such as Infrai, one key and one REST surface can cover the DNS lookup, record listing, and document-generation steps, reducing credential and billing plumbing for a solo team. That convenience is not universal: if your organization requires AWS-native IAM and CloudTrail, Route 53 is the better fit; if you need self-hosted authority, choose PowerDNS. A unified service also leaves you responsible for zone ownership, propagation messaging, and export requirements.

How do you keep verification and documentation synchronized?

Treat the record set as a versioned contract. Store a hash or revision alongside the tenant’s pending verification, and include that revision in the generated document metadata. When a record changes, invalidate the old document and issue a new one. A customer may forward yesterday’s PDF after today’s token rotation; an explicit revision lets support identify that case without guessing.

Propagation is another boundary. Correct instructions do not make DNS instant, and a successful lookup from one resolver does not prove every resolver has converged. Report what your verifier checked, including the record name and type, and let the user retry. Do not tell customers to edit a record until it “looks right” in a dashboard; exact content is the evidence.

I initially treated the document as presentation. The design changed once the failure mode was clear: presentation is a projection of the check, so it must be generated beside the check. That is a small architectural constraint with a large support payoff.

Decision rule

Choose the DNS provider that matches your existing zone ownership and operational controls. Choose the documentation design that cannot invent a different record set. If your team is small and you value a single, discoverable REST surface, a unified API may shorten integration work; Cloudflare, Route 53, Google Cloud DNS, and PowerDNS remain valid when their surrounding ecosystem is the better fit.

Before shipping, run a change test: alter one required TXT value, regenerate the customer document, and confirm the verifier and the rendered content change together. If they do not, the system still has two sources of truth.

References

Top comments (0)