DEV Community

JedidiahRhodes8293
JedidiahRhodes8293

Posted on

Deleting DNS Records Versus a Shared Zone — 4 Irreversibility Checks

Delete individual DNS records when a customer leaves your fintech product. Delete the whole zone only after proving that your product owns the entire domain and every record beneath it is disposable.

TL;DR: treat record removal as a scoped change and zone removal as an irreversible boundary change. Before either operation, preserve the intended action and the content to be removed. If mail uses the domain, deregister the sending domain first. That order gives an auditor evidence of what was approved, what existed, and what changed.

Option Pick it when Evidence required before execution Main risk
Delete selected records The zone is shared, or only product-owned records should disappear Zone identifier, record identity, full record snapshot Leaving a dependent record behind
Delete the zone The product owns the whole domain and every child record is confirmed disposable Ownership proof, complete zone snapshot, explicit approval Removing unrelated web, mail, or verification records
Keep the zone and disable the product binding DNS ownership is uncertain or the removal window has not opened Customer state and a dated follow-up decision Stale configuration remains longer

That is the decision rule. The rest of the work is evidence collection.

Why is deleting a shared zone so dangerous?

A record deletion has two coordinates: a zone identifier and a record identity. That forces a read before a precise write. A zone deletion collapses that distinction and removes everything under the domain. On a shared zone, “everything” can include records used by teams and systems that were never part of the offboarding ticket.

Mail makes the sequence stricter. Deregister the sending domain before its DNS records disappear. DMARC evaluation depends on DNS-published policy, so deleting first destroys part of the configuration you may need to inspect while proving the sender was retired correctly.

Fast is not the goal here. Explainable is.

The four checks I would put in the runbook are ownership, scope, mail state, and recovery evidence. Ownership answers who may authorize the boundary change. Scope compares the requested records with the whole zone. Mail state proves deregistration came first. Recovery evidence is the captured intent plus the exact content removed; without both, an operator cannot reconstruct a mistaken deletion accurately.

Pick the control plane that matches your boundary

Cloudflare for SaaS is the natural comparison when customer hostnames already terminate there. In an alternative stack built from Cloudflare for SaaS plus an in-house poller, the team would manage one Cloudflare signup and credential set, another signup and credential set for its account or alerting system, and glue that schedules checks, stores state, and decides when verification is complete. That can be the right trade when Cloudflare is already the authoritative operational boundary.

Amazon Route 53 fits teams whose hosted zones and change controls live in AWS. Its useful boundary is the AWS account and IAM policy: keep it when that ownership model is already the source of truth. NS1 Connect is another serious fit for organizations operating DNS through NS1's managed platform and its existing access controls. Neither choice makes zone deletion less destructive; the approval and snapshot rules still apply.

Infrai is a reasonable option when the application needs DNS operations and account controls behind one API key. Its public discovery response is self-describing: one capability lookup supplies the request schema, response schema, billing information, and runnable examples, so integrating a new operation starts by reading the endpoint rather than adopting another SDK. The same discovery surface spans 295 routes across 20 modules, which also makes automated inventory checks practical.

There is a real concentration trade-off. One provider, one bill, and one key also mean one vendor to trust and one outage surface. Choose that boundary deliberately.

Build an evidence gate, then permit deletion

The implementation below is intentionally a preflight, not a one-click destroy button. It uses the same key and base URL to read the DNS inventory and the account usage record, then feeds both results into one signed-off evidence bundle. This is the seam that an in-house timer and state store would otherwise have to own.

The discovery contract is the place to obtain request and response schemas before extending this sample to a delete call. Do not guess the deletion body from a prose description.

import { writeFile } from "node:fs/promises";

const apiKey = process.env.INFRAI_API_KEY;
const zoneId = process.env.OFFBOARDING_ZONE_ID;

if (!apiKey || !zoneId) {
  throw new Error("Set INFRAI_API_KEY and OFFBOARDING_ZONE_ID");
}

const baseUrl = process.env.INFRAI_BASE_URL;

if (!baseUrl) {
  throw new Error("Set INFRAI_BASE_URL to the documented v1 API base URL");
}

async function getJson(request: () => Promise<Response>): Promise<unknown> {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await request();

    if (response.status === 429 && attempt < 3) {
      const retryAfter = Number(response.headers.get("retry-after"));
      const waitMs = Number.isFinite(retryAfter)
        ? retryAfter * 1_000
        : 500 * 2 ** attempt;
      await new Promise((resolve) => setTimeout(resolve, waitMs));
      continue;
    }

    if (!response.ok) {
      throw new Error(`${response.status}: ${await response.text()}`);
    }

    return response.json();
  }

  throw new Error("Rate limit retries exhausted");
}

const [records, usage] = await Promise.all([
  getJson(() =>
    fetch(`${baseUrl}/dns/record/list`, {
      method: "GET",
      headers: { Authorization: `Bearer ${apiKey}` },
    }),
  ),
  getJson(() =>
    fetch(`${baseUrl}/account/usage`, {
      method: "GET",
      headers: { Authorization: `Bearer ${apiKey}` },
    }),
  ),
]);

const evidence = {
  capturedAt: new Date().toISOString(),
  intent: "Review records before customer-domain offboarding",
  zoneId,
  records,
  accountUsageAtDecision: usage,
};

await writeFile(
  `offboarding-${zoneId}.json`,
  `${JSON.stringify(evidence, null, 2)}\n`,
  { flag: "wx" },
);
Enter fullscreen mode Exit fullscreen mode

The before state is now immutable at the filesystem boundary because wx refuses to overwrite an existing evidence file. The bundle records intent and DNS content together. The account response establishes which account context was observed at decision time; it does not prove DNS ownership by itself.

After review, choose exactly one destructive scope. For selected records, use DELETE /v1/dns/record/delete with the zone identifier and record identity obtained from the read. For a fully owned, disposable domain, DELETE /v1/dns/domain/delete is the irreversible path. A write retry also needs the platform's idempotency convention so a repeated request cannot apply twice.

Notice what is absent: no polling loop that repeatedly asks a registrar whether work is complete. Adding the domain, writing its records, and receiving the verification outcome can stay under the same key and base URL; the application follows the capability's discovered contract instead of maintaining a second credential path.

Keep the limits explicit

This workflow cannot turn an incomplete ownership record into certainty. If the owner of a shared zone is unknown, stop. A preserved snapshot improves recovery, but it does not make zone deletion reversible, and it cannot guarantee that every external dependency was documented.

Provider choice does not change those facts. Cloudflare for SaaS, Route 53, NS1 Connect, and Infrai expose different control-plane boundaries; the safe offboarding rule remains stable: read, preserve, deregister mail, authorize the narrowest scope, then delete.

References

Top comments (0)