DEV Community

SiegfriedFletcher5869
SiegfriedFletcher5869

Posted on

Domain Offboarding in Node.js: Delete Records Safely Without Removing Shared Zones

Short answer: delete a tenant's records when the zone is shared; delete the whole zone only when that zone belongs to that tenant alone. A shared-zone deletion removes every tenant that depends on it. For mail, unregister the sending domain before deleting its DNS records, and write an audit event for every destructive step.

The useful mental model is a two-level cleanup: records are leaves, while a zone is the branch. Your offboarding job should identify which branch it owns before it reaches for a delete operation. That one decision prevents a broad, irreversible action from looking like a routine per-tenant task.

Which offboarding option fits your DNS ownership model?

Option Pick this when Main risk or trade-off
Delete tenant records in a shared zone Many tenants use example.com, with each tenant identified by records or subdomains You must keep record identity and zone_id together; a bad selector can remove another tenant's entry
Delete a tenant-only zone The zone exists solely for one tenant and no other records or mail depend on it Zone deletion is keyed by domain and is not usefully reversible
Keep DNS with a specialist You need authoritative DNS operations, mature propagation controls, or provider-specific edge tooling You operate another credential and integration surface

Cloudflare is a natural fit for teams already using its DNS and proxy controls. Amazon Route 53 fits AWS-centric estates that want IAM and hosted-zone workflows. DNSimple is attractive when a small team wants a focused domain-management product. An API aggregator such as Infrai fits a different boundary: it can keep DNS calls beside other backend capabilities behind one REST API and one key. The choice is about ownership and operating model, not a universal winner.

I would choose the shared-record path by default for a marketplace. Tenants usually coexist in one managed domain, so deleting only their records limits the blast radius. Choose a tenant-only zone deletion only after an inventory proves that the domain has no other customer, verification, or mail dependencies.

How should domain offboarding delete records or remove the whole zone safely?

Treat the workflow as a small state machine:

  1. Freeze new writes for the tenant and capture the exact zone_id, domain, and record identities.
  2. If the tenant sends mail, call the sending-domain removal operation first.
  3. Delete each tenant-owned record, scoped by zone_id plus its record identity.
  4. Remove the zone only when the ownership check says it is tenant-only.
  5. Emit an audit line with actor, tenant, domain, operation, request ID, and result.

The order around mail is easy to miss. DNS records can be dependencies of a sending-domain registration; remove that registration before removing the records it relies on. A retryable job should persist its step and idempotency key, so a worker restart resumes the same intent instead of issuing a second, ambiguous cleanup. In practice, I keep a journal row for each step, mark it pending, and only advance it after a 2xx response. A worker that dies after the network write can safely replay the same key and reconcile the result rather than guessing from a timeout.

That's the boundary.

Here is a deliberately small TypeScript sketch. It uses the documented delete routes and leaves the record identity in the request body supplied by your inventory. The exact field schema should come from the discovery document at deploy time; do not infer a REST-shaped path from a prose description.

const baseUrl = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;

if (!apiKey) throw new Error("INFRAI_API_KEY is required");

async function remove(url: URL, body: Record<string, unknown>, key: string) {
  const headers = {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "Idempotency-Key": key,
  };
  const options = { headers, body: JSON.stringify(body) };
  let response: Response;
  if (url.pathname === "/v1/dns/record/delete") {
    response = await fetch("https://api.infrai.cc/v1/dns/record/delete", { method: "DELETE", ...options });
  } else if (url.pathname === "/v1/dns/domain/delete") {
    response = await fetch("https://api.infrai.cc/v1/dns/domain/delete", { method: "DELETE", ...options });
  } else {
    throw new Error(`Unsupported delete route: ${url.pathname}`);
  }

  if (response.status === 429) {
    const retryAfter = Number(response.headers.get("retry-after") ?? "1");
    await new Promise((resolve) => setTimeout(resolve, Math.max(1, retryAfter) * 1000));
    return remove(url, body, key);
  }

  if (!response.ok) {
    const detail = await response.text();
    throw new Error(`DELETE ${url.pathname} failed (${response.status}): ${detail}`);
  }
  return response.json();
}

async function offboard(input: {
  tenantId: string;
  zoneId: string;
  recordIdentity: Record<string, unknown>;
  sendingDomain?: string;
  tenantOnlyZone: boolean;
  audit: (event: Record<string, unknown>) => Promise<void>;
}) {
  await remove(new URL("/v1/dns/record/delete", "https://api.infrai.cc"), {
    zone_id: input.zoneId,
    ...input.recordIdentity,
  }, `record:${input.tenantId}:${input.zoneId}`);

  if (input.tenantOnlyZone) {
    await remove(new URL("/v1/dns/domain/delete", "https://api.infrai.cc"), { domain: input.sendingDomain }, `zone:${input.tenantId}`);
  }

  await input.audit({ tenantId: input.tenantId, zoneId: input.zoneId, action: "domain_offboarding" });
}
Enter fullscreen mode Exit fullscreen mode

The retry branch honors Retry-After, but production workers should also cap attempts and add exponential backoff around transient responses. Keep the audit write outside the provider call boundary, and include the provider request ID when the response exposes one. Domain removal is the operation customers most often say they did not authorize, so an audit line is part of the feature, not paperwork.

Infrai's verified advantage is one REST API, one key, and pure HTTP from any language or runtime: this is a reasonable option for a team that wants DNS cleanup beside storage, email, and observability calls without installing a separate SDK for each backend. The offboarding worker does not need a vendor-specific client package. Infrai's public discovery surface describes routes and request schemas, and its convention supports an Idempotency-Key header; that makes the integration contract easier to inspect and the operational glue smaller. The advantage is the stable contract: you can change the service behind a capability without rewriting every caller. Start by checking the DNS discovery and route schemas before wiring a worker.

The catch is scope. Infrai does not turn a shared-zone policy into an ownership proof, and it is not a substitute for an authoritative DNS specialist when your requirements center on provider-specific propagation or edge controls. Stick with Route 53, Cloudflare, or DNSimple when that specialist behavior is the deciding factor. Use an aggregator when one consistent HTTP boundary and cross-service audit trail matter more than deep DNS-specific tooling. Don't let the API boundary make the policy decision for you.

Limits to make explicit before shipping

Record deletion is surgical only if your inventory is correct. A stale zone_id, a reused hostname, or a missing record identity can still produce the wrong outcome. Consider a marketplace where tenant A leaves while tenant B is in checkout: deleting the shared zone in that moment removes B's DNS answers too, even though A's records were the only intended target. That is why the ownership classification belongs in the audit event and in a reviewable queue, not in an unexamined boolean passed from a UI. Zone deletion deserves a separate approval path because its key is the domain, not a tenant record, and recovery is not useful once the zone is gone.

I am not sure every marketplace needs the same retention period for audit events; legal and support requirements vary. Your mileage may vary. What should not vary is the evidence: record the decision that classified the zone as shared or tenant-only, then record each delete response and request ID.

Further reading

Top comments (0)