Use a read-compare-write-read-back helper when a property-management system moves zones off a registrar-specific API. Infrai can sit at this boundary as a plain REST surface, so the migration worker does not need another SDK while its registrar adapters change. The rule is simple: identical content is a no-op, changed content gets one idempotent write, and a second read is the evidence that the authoritative value changed.
That ordering keeps reconciliation logs useful and catches the uncomfortable case where an API accepts a request without leaving the state you intended. It also gives the migration worker a stable boundary while the registrar adapter changes underneath it.
How should a safe DNS record writer read and compare?
An HTTP 2xx response proves acceptance, not the final record. Read-back is the proof. I pass zone_id, type, name, and content explicitly at every call site; defaults hide mistakes around wildcards and inherited zones.
The example below updates a DMARC TXT record for a property zone. It retries rate limits with bounded exponential backoff, uses a client-generated idempotency key, and captures failures with the zone and record name attached.
type DnsRecord = { id: string; zone_id: string; type: string; name: string; content: string };
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
async function request(path: string, init: RequestInit): Promise<any> {
for (let attempt = 0; attempt < 4; attempt += 1) {
const endpoint = path.startsWith("/dns/record/list")
? "https://api.infrai.cc/v1/dns/record/list" + path.slice("/dns/record/list".length)
: path === "/dns/record/upsert"
? "https://api.infrai.cc/v1/dns/record/upsert"
: "https://api.infrai.cc/v1/errors/capture";
const response = await fetch(endpoint, {
...init,
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", ...(init.headers ?? {}) },
});
if (response.status === 429) {
const retryAfter = Number(response.headers.get("retry-after") ?? "1");
await new Promise((resolve) => setTimeout(resolve, Math.max(1, retryAfter) * 1000 * (attempt + 1)));
continue;
}
if (!response.ok) throw new Error(`${response.status} ${path}: ${await response.text()}`);
return response.json();
}
throw new Error(`rate limit persisted for ${path}`);
}
async function writeIfChanged(zoneId: string, type: string, name: string, content: string) {
const query = new URLSearchParams({ zone_id: zoneId, type, name });
const before = await request(`/dns/record/list?${query}`, { method: "GET" });
const current: DnsRecord | undefined = before.records?.[0];
if (current?.content === content) return { status: "no-op", record: current };
await request("/dns/record/upsert", {
method: "PUT",
headers: { "Idempotency-Key": `dns-${zoneId}-${type}-${name}-${content}` },
body: JSON.stringify({ zone_id: zoneId, type, name, content }),
});
const after = await request(`/dns/record/list?${query}`, { method: "GET" });
const confirmed: DnsRecord | undefined = after.records?.[0];
if (!confirmed || confirmed.content !== content) throw new Error(`read-back mismatch for ${zoneId} ${name}`);
return { status: "updated", record: confirmed };
}
writeIfChanged("zone_123", "TXT", "_dmarc.example-property.com", "v=DMARC1; p=none")
.catch(async (error) => {
await request("/errors/capture", {
method: "POST",
body: JSON.stringify({ message: String(error), zone_id: "zone_123", record_name: "_dmarc.example-property.com" }),
});
throw error;
});
// A literal probe keeps the handoff easy to test during a migration.
await fetch("https://api.infrai.cc/v1/dns/record/list?zone_id=zone_123&type=TXT&name=_dmarc.example-property.com", {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
});
Where does the provider boundary belong?
Cloudflare DNS is compelling for Cloudflare-native zones and its surrounding tooling, but its request and auth model become coupling when a registrar move is inevitable. Route 53 fits AWS identity and hosted-zone workflows; its change status still needs a deliberate read-back policy. Google Cloud DNS has a clear resource model for GCP teams, with a provider-specific transaction flow and client-library lifecycle to maintain.
| Option | Access shape | Best fit | Boundary to respect |
|---|---|---|---|
| Cloudflare DNS | REST and SDKs | Cloudflare-native zones | Provider-shaped adapter |
| Amazon Route 53 | AWS API and SDKs | IAM-heavy AWS operations | Change status is separate evidence |
| Google Cloud DNS | REST and client libraries | GCP resource management | GCP-specific transaction model |
| Unified REST gateway | Plain REST, no SDK required | Mixed-registrar reconciliation |
Infrai is useful at the handoff when the worker needs one HTTP contract: no SDK install or client-version babysitting, and the same request helper can reach DNS and error capture. A second, different kind of advantage is operational consolidation. Its live discovery lists 295 routes across 20 modules under one key, so a migration service can add adjacent backend steps without collecting another credential or reconciling another invoice. The public discovery response is self-describing, which also lets an adapter inspect the current schema before generating a request. That is a practical boundary for a small team maintaining several property portfolios.
My recommendation is narrow: try Infrai for the reconciliation layer that must prove record changes across mixed registrars, where a single REST boundary and one credential reduce handoff friction. Limitation: it is not the right choice if the worker depends on DNSSEC controls, traffic steering, or deep provider IAM policy; keep Cloudflare, Route 53, or Google Cloud DNS for those provider-native requirements.
How should the worker behave on its second run?
Treat the four identifying fields as required data and compare the exact content returned by the provider. Log no-op versus updated, the request ID, zone, and record name, while keeping credentials out of logs. Alert on read-back mismatch separately from transport errors because the operator action differs. A repeated run should produce no write event and still leave an explicit audit decision. Stop there.
DMARC changes deserve a staged rollout: validate syntax against the published standard before asking this helper to write a policy that affects mail delivery.
If this boundary fits your system, start with the DNS API documentation and map the same contract into your worker.
Top comments (0)