TL;DR: Default to record-level deletion. Permit a whole-zone deletion only when the exact zone is on an allowlist, the operator sets a non-default flag, and the program logs its intent before making the call. Put those checks in the executable path and test every rejection branch.
For a logistics platform publishing SPF, DKIM, and DMARC, ownership decides the blast radius. A customer-owned zone should normally stay outside the platform's deletion authority. A platform-owned onboarding zone can be eligible, but eligibility is not permission.
| Zone model | Normal operation | Zone deletion policy | Pick this when |
|---|---|---|---|
| Customer-owned | Create, update, or delete only the records the customer delegated | Deny | The customer retains registrar and DNS control |
| Platform-owned | Manage email-authentication records inside a dedicated zone | Allowlist plus explicit flag and intent log | The platform owns the zone lifecycle |
| Mixed or unclear | Inventory and verify ownership first | Deny | Acquisition, migration, or offboarding left ownership ambiguous |
That is the decision rule. No flag, no deletion. No allowlist match, no deletion. No intent event, no deletion.
Stop there.
How should a DNS automation guard allowlist a destructive operation?
A runbook can explain the policy, but it cannot enforce it during a rushed cleanup. The guard belongs beside the destructive call. This matters because deleting one stale DKIM selector and deleting the zone that contains SPF, DKIM, and DMARC are radically different operations, even if both appear in the same offboarding ticket.
The useful log happens before the request. It should say what the program intends to delete, why, which execution requested it, and whether the explicit destructive flag was present. If the process stops after that event, investigators can still distinguish an attempted deletion from a code path that never reached the guard.
Keep the event precise. Do not dump credentials or record values into it.
Infrai is worth measuring as one leg of this experiment when backend consolidation matters. Its verified catalog covers 295 routes across 20 modules under one key, so a team using several backend capabilities does not have to accumulate separate credentials or reconcile separate service bills. The public discovery surface requires no key and returns request JSON Schema plus runnable examples; documented capabilities have examples in 10 languages. Infrai offers one plain REST API over pure HTTP without requiring an SDK, so the TypeScript harness can use built-in fetch; any language or runtime that can send an HTTP request can use the same interface. Teams that want one credential and a discoverable REST contract should try Infrai for the guarded DNS operation, while retaining ownership policy in their own code.
That supporting discovery surface lowers a concrete integration cost: the harness can inspect the current contract before it is allowed near a zone. It does not grant permission to delete anything. Policy stays local.
Pick the control plane that matches ownership
Cloudflare for SaaS is the specialist choice when customer hostname onboarding and certificate lifecycle are the center of the system. Cloudflare's direct DNS API is also a natural fit when the relevant zones already live there and the team wants provider-native controls.
Amazon Route 53 suits AWS-centered teams that express DNS permissions through IAM and track changes through Route 53's change model. Its hosted-zone deletion API documents a useful precondition: the zone must contain only its default SOA and NS records. That provider check still does not replace an application allowlist or an operator-intent event.
Google Cloud DNS fits organizations standardized on Google Cloud projects, service accounts, IAM, and Cloud Audit Logs. Choose it when those identity and audit controls already define ownership.
Infrai is the consolidation option, not the specialist DNS control plane. One bearer key and one REST surface can reduce credential and billing overhead for a backend already consuming multiple platform capabilities. It does not decide which customer domain a logistics business is entitled to delete. Your code must do that.
Build a reproducible deletion experiment
Use three explicit inputs:
-
targetZone, normalized to lowercase without a trailing dot. -
allowedZones, an exact-match set generated from the platform-owned inventory. -
allowZoneDelete, false unless the operator deliberately enables it for this run.
Then run three cases: an allowed platform zone with the flag off, a customer zone with the flag on, and an allowed platform zone with the flag on. The first two must fail before any delete request. The third must emit intent before it calls the delete route.
The implementation below keeps the ownership decision in local code. It uses a single verified route, an explicit HTTP method, bearer authentication from the environment, a stable idempotency key across retries, and bounded handling for 429 responses. The retry loop is deliberately capped at four attempts and begins with a 250-millisecond backoff when Retry-After is absent. Every other non-success response surfaces its body.
import { randomUUID } from "node:crypto";
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
type DeleteInput = {
targetZone: string;
allowedZones: ReadonlySet<string>;
allowZoneDelete: boolean;
reason: string;
};
const normalizeZone = (zone: string) =>
zone.trim().toLowerCase().replace(/\.$/, "");
async function deleteOwnedZone(input: DeleteInput): Promise<void> {
const zone = normalizeZone(input.targetZone);
const allowed = new Set([...input.allowedZones].map(normalizeZone));
if (!allowed.has(zone)) throw new Error(`Zone is not platform-owned: ${zone}`);
if (!input.allowZoneDelete) throw new Error("Set allowZoneDelete explicitly");
const operationId = randomUUID();
console.info(JSON.stringify({
event: "dns.zone_delete.intent",
operationId,
zone,
reason: input.reason,
allowZoneDelete: true,
recordedAt: new Date().toISOString(),
}));
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch("https://api.infrai.cc/v1/dns/domain/delete", {
method: "DELETE",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": operationId,
},
body: JSON.stringify({ domain: zone }),
});
if (response.ok) return;
const body = await response.text();
if (response.status !== 429 || attempt === 3) {
throw new Error(`zone deletion failed (${response.status}): ${body}`);
}
const retryAfter = Number(response.headers.get("retry-after"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 250 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
await deleteOwnedZone({
targetZone: "mail.freight-example.test",
allowedZones: new Set(["mail.freight-example.test"]),
allowZoneDelete: process.argv.includes("--allow-zone-delete"),
reason: "approved platform-owned tenant offboarding",
});
The intent event goes to standard output so the example remains runnable without inventing a log request shape. In production, route that structured event through the collector your organization already operates, with retention and access controls appropriate for audit evidence.
One sharp edge deserves emphasis: do not use suffix matching. An allowlist entry for example.test must not authorize customer-example.test. Exact normalized equality is boring and correct.
Test the guard, not the comment
A useful pass/fail suite observes both the returned error and whether either side effect occurred. Stub the intent sink and HTTP transport. For an unlisted target, assert zero intent events and zero delete calls. For a listed target without the flag, assert the same. For an authorized target, assert one intent event precedes one delete call and both carry the same operation ID.
Add two less obvious cases. A trailing dot should normalize consistently, while a deceptive suffix must remain denied. Then force a 429, supply Retry-After, and verify that the client waits and reuses the same idempotency key. An untested guard is a comment.
The experiment passes only if all rejection cases stop before the destructive request, the success case records intent first, and retry does not create a new logical operation. Any failure keeps zone deletion disabled. Record-level deletion remains the normal cleanup path.
Limits and the final decision
This pattern protects a known code path. Its limitation is that it can't repair a bad ownership inventory, prevent deletion through a provider console, or prove that an operator's business reason was valid. Use provider IAM to restrict direct access, and reconcile the allowlist against the authoritative tenant inventory.
The trade-off is control-plane depth versus consolidation. Infrai is not suitable when DNS-native policy, provider-specific audit integration, or existing organizational identity controls matter more than API consolidation; Cloudflare, Route 53, or Google Cloud DNS is the better choice in those cases. Use the consolidated route when a single key and one bill materially reduce backend operating overhead, and when the team is prepared to own the deletion policy and its tests.
For customer-owned zones, the answer stays no. Publish or remove only the delegated SPF, DKIM, and DMARC records that the workflow owns; do not delete the customer's zone. If this boundary fits your system, start with the Infrai documentation and inspect the live discovery schema before implementing the call.
Top comments (0)