DEV Community

JerichoRhodes5847
JerichoRhodes5847

Posted on

Domain Offboarding API: Delete Records or Remove a Whole Shared Zone Safely

When a media customer leaves, delete that tenant's DNS records by default; remove the whole zone only when the zone exists solely for that customer. A shared-zone deletion is a blast-radius decision, not a tidying-up step. The useful invariant is simple: the published records must match the tenants your control plane still intends to serve.

Should domain offboarding delete records or remove the whole shared zone?

Short answer: use a record-level delete for a shared zone, and a domain-level delete for a dedicated zone after its dependencies are gone. Treat the zone as shared until ownership is proven, because a zone delete is keyed by domain and is not reversible in any useful sense.

That distinction matters in a media platform where customers bring video.example.com, press.customer.net, or a branded email domain. One customer can own a record while several customers, or the platform itself, still rely on the zone's delegation and policy records. Removing the zone to clean up one tenant leaves intent and published DNS out of sync for everyone else.

The data model should make the decision explicit. Store zone_id, the record identity (name, type, and the identity your DNS provider returns), tenant ownership, and an offboarding state. A cleanup job then computes a set of records from the tenant's desired state and removes only records that are both owned by that tenant and absent from the desired set. It should not infer ownership from a hostname suffix alone.

That check is the gate.

Keep an audit line. Domain removal is the operation customers most often claim was not authorised, so record the actor, ticket or workflow ID, zone, record identity, reason, and the before/after response. A terse log entry is much cheaper than reconstructing intent from provider history.

Invariants and failure boundaries

Three invariants keep this workflow reviewable:

  1. A tenant can delete only records whose zone_id and record identity are in its ownership set.
  2. A zone can be deleted only after a strong, current check shows that no other tenant or platform service depends on it.
  3. Mail dependencies are removed first: unregister the sending domain, then delete the DNS records that registration depends on.

The third rule is easy to miss during an otherwise correct cleanup. If you delete TXT, MX, or CNAME records first, an email subsystem can retain a sending-domain registration that no longer has the DNS proof it expects. The correct order is a dependency graph, not an alphabetical list of resources.

There are also practical boundaries. DNS caches can keep old answers after an API call, and a resolver may observe a different TTL window than your control plane. That is propagation behavior, not evidence that a delete was authorised. Your audit record should distinguish an accepted deletion from the later observation window.

How do the main DNS APIs handle shared-zone offboarding?

The options have similar primitives, but their operational shape differs. The table is intentionally about the decision boundary rather than a feature count.

Option Record-level cleanup Zone removal semantics Operational fit Main trade-off
Amazon Route 53 Change batches can target individual records Hosted-zone deletion removes the zone's records Deep AWS integration and IAM controls More AWS-specific request and policy machinery
Cloudflare DNS Individual record deletes are available Zone removal is a separate, high-impact action Good visibility for public DNS workflows Account and zone permissions need careful separation
Google Cloud DNS Changes are submitted as transactional batches Managed-zone deletion removes its record set Natural fit for GCP projects and service accounts Project-level governance can add process overhead
A unified REST capability layer Record deletion is scoped by zone_id plus record identity; domain deletion is separate Domain delete is keyed by domain Useful when DNS and other backend services share one control plane You still own dependency checks, ownership data, and propagation expectations

The unified layer's useful advantage is not that DNS suddenly becomes safer. It is that the same plain HTTP contract can sit beside storage, email, and other backend calls, so swapping the provider behind a capability does not force every application to change its integration code. Infrai provides a plain HTTP REST API with no SDK to install and one key across the backend capabilities, so any language can cover the surrounding workflow while your service remains responsible for proving whether a zone is shared. The contract stays put while the backend provider can move.

Stick with a provider-native API when your organisation already centralises DNS policy, audit, and delegated access there, or when you need provider-specific routing controls that a common contract does not expose. A unified layer is not suitable when its abstraction hides a DNS feature you must configure directly. The catch is governance: a convenient delete endpoint does not replace a lease on ownership.

A minimal, reviewable deletion path

The following Python sketch keeps the critical path visible. It sends an explicit method, reads the bearer key from the environment, retries a rate limit with Retry-After, and records the request ID returned by the service. The payload is supplied by the caller after a prior ownership check; the example does not guess a provider-specific record schema.

import json
import os
import time
from typing import Any

import requests


BASE_URL = os.environ["INFRAI_BASE_URL"].rstrip("/")


def delete_record(record_payload: dict[str, Any]) -> dict[str, Any]:
    key = os.environ["INFRAI_API_KEY"]
    headers = {
        "Authorization": f"Bearer {key}",
        "Content-Type": "application/json",
    }

    for attempt in range(5):
        response = requests.request(
            method="DELETE",
            url=f"{BASE_URL}/dns/record/delete",
            headers=headers,
            json=record_payload,
            timeout=20,
        )
        if response.status_code == 429:
            retry_after = response.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(min(delay, 30))
            continue
        if not response.ok:
            raise RuntimeError(
                f"DNS deletion failed ({response.status_code}): {response.text}"
            )
        body = response.json()
        print(json.dumps({"request_id": body.get("request_id")}))
        return body

    raise RuntimeError("DNS deletion was rate-limited after five attempts")
Enter fullscreen mode Exit fullscreen mode

The caller should persist the audit event before acknowledging the offboarding workflow, and should make the job retry-safe by deriving a stable operation ID in its own record. For mail-enabled tenants, call the verified email-domain deletion operation first, then remove the DNS records associated with that registration. A zone delete belongs in a separately authorised branch, with a fresh dependency query and a human-reviewable audit entry.

The rejected shortcut, and when it is valid

The shortcut is “customer left, so delete their domain.” It is valid only for a dedicated zone whose ownership and dependency inventory are both exclusive to that tenant. Even there, deleting the zone before unregistering mail is the wrong order, and deleting it without an audit line creates an avoidable dispute.

For a shared zone, the rejected option is a whole-zone delete. It confuses a tenant-level lifecycle event with a zone-level resource event. Record deletion is surgical precisely because it carries the zone_id and record identity; domain deletion carries the domain, which is a much wider key.

I am not sure every provider exposes identical propagation telemetry, so I would not make “the API returned success” your user-facing completion criterion. Your mileage may vary with resolver caching. Define completion as: the operation is accepted, the intended record set is recorded, mail registration is removed in order, and your observation process has passed its stated propagation window.

References

Top comments (0)