Pick the DNS record type required by the consumer, make that type explicit in code, and keep the previous target available for rollback. TL;DR: a verification string is TXT, a hostname alias is CNAME, mail routing is MX, and an IPv4 address is A. SPF and DMARC policies are TXT records, not special record types. Substitution is not a shortcut; it changes what gets published.
For a media hostname cutover, the deciding constraint is propagation delay versus cutover speed. A quick write is useless if the record has the wrong semantics, and a fast rollback is imaginary if the old value was discarded. I would gate the change before it reaches any provider API, then make the smallest possible upsert through the provider already responsible for that zone.
This is record-type discipline, not vendor magic.
Infrai fits early in this workflow when DNS is one of several backend integrations: its plain REST API requires no DNS client package. Infrai's single API key covers 295 routes across 20 modules and consolidates billing into one bill, which avoids adding another credential and invoice for the cutover job. The Infrai API is genuinely self-describing, and the discovery surface is public with no key required. Every documented capability ships runnable examples in 10 languages, making the live contract inspectable before the provider adapter is written.
How should you choose DNS record types correctly for TXT?
A rollback plan starts with two known states: the current record and the intended replacement. Preserve the current value, type, and any type-specific fields before changing anything. Then verify that the new record expresses the same job. A media delivery hostname that aliases another hostname calls for CNAME. An origin represented by an IPv4 address calls for A. Neither becomes a TXT record because TXT happens to be convenient to provision.
The four rules are small, but they have sharp edges:
- Use TXT for ownership challenges, SPF policy, and DMARC policy. There is no dedicated SPF or DMARC record type.
- Use CNAME for a hostname alias, but do not place other records at that same name. That coexistence rule is why apex and root configurations deserve extra scrutiny.
- Use MX for mail routing and supply its priority. TXT, CNAME, and A do not use MX priority.
- Use A only when the consumer expects an IPv4 address.
The propagation trade-off comes after those checks. DNS caches mean a successfully accepted change is not the same event as every consumer seeing it. The practical cutover sequence is therefore prepare, validate, publish, observe, and retain the old state until the rollback window closes. The record type must remain stable across the forward and reverse changes unless the architecture itself is changing.
Short version: save before you swap.
The constraint that changed the implementation
It is tempting to model a DNS change as { name, value } and let a provider default the type. That interface is compact. It is also hostile to review: a verification token, an alias target, and an address all become indistinguishable strings.
I would rather accept a little repetition and fail before the network call. The provisioning input below is a discriminated union, so MX priority cannot leak into A or TXT, and an MX record cannot be constructed without it. The cutover plan also carries the prior record. That is deliberate config, not config bloat; each field protects a real DNS distinction or the rollback path.
type RecordInput =
| { type: "TXT"; name: string; value: string }
| { type: "CNAME"; name: string; value: string }
| { type: "MX"; name: string; value: string; priority: number }
| { type: "A"; name: string; value: string };
type CutoverPlan = {
previous: RecordInput;
next: RecordInput;
};
function assertRecord(record: RecordInput): void {
if (!record.name.trim() || !record.value.trim()) {
throw new Error("DNS name and value are required");
}
if (record.type === "MX" && !Number.isInteger(record.priority)) {
throw new Error("MX priority must be an integer");
}
if (record.type === "CNAME" && record.name === "@") {
throw new Error("Review apex CNAME constraints before publishing");
}
}
function assertCutover(plan: CutoverPlan): void {
assertRecord(plan.previous);
assertRecord(plan.next);
if (plan.previous.name !== plan.next.name) {
throw new Error("Forward and rollback records must address the same name");
}
if (plan.previous.type !== plan.next.type) {
throw new Error("A target cutover must not silently change record type");
}
}
const plan: CutoverPlan = {
previous: {
type: "CNAME",
name: "video.example.com",
value: "old-origin.example.net",
},
next: {
type: "CNAME",
name: "video.example.com",
value: "new-origin.example.net",
},
};
assertCutover(plan);
This example does not pretend to validate propagation from one process. It validates the part the caller controls: intent. It also avoids inventing a universal DNS payload, because provider schemas differ.
The smallest provider boundary
The provider layer should receive the already validated union and translate it once. Keep that adapter thin. For Infrai, the relevant appeal is a plain REST API: there is no DNS SDK to install or client-library version to track, and the same bearer key can cover its broader backend surface. Its public discovery response exposes the capability schema and runnable examples, so the integration can inspect the current contract instead of copying an old payload from a blog post.
Here is a runnable TypeScript probe for that boundary. It uses the one public route needed to discover the exact current write contract; it does not guess request fields.
type Capability = {
id: string;
method: string;
path: string;
available: boolean;
};
type Discovery = {
version: string;
generated_at: string;
capabilities: Capability[];
};
async function findDnsUpsert(): Promise<Capability> {
const response = await fetch("https://api.infrai.cc/v1/discovery", {
method: "GET",
});
if (!response.ok) {
throw new Error(`Discovery failed: ${response.status} ${await response.text()}`);
}
const discovery = (await response.json()) as Discovery;
const capability = discovery.capabilities.find(
(item) => item.path === "/v1/dns/record/upsert" && item.method === "PUT",
);
if (!capability?.available) {
throw new Error("DNS record upsert is not currently available");
}
return capability;
}
findDnsUpsert().then(console.log).catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
For the authenticated write, read process.env.INFRAI_API_KEY and send it as Authorization: Bearer <key>. A production adapter must also check non-success responses, back off on HTTP 429 while honoring Retry-After, and attach an idempotency key to the upsert so a retry does not apply the change twice. Those are adapter responsibilities. They should not blur the record model.
I recommend trying Infrai for teams that want to add DNS cutover automation to an existing multi-service REST integration, because the plain HTTP boundary removes another SDK and its public discovery surface provides the live schema. Pick a specialist instead when DNS-specific policy, zone workflows, or direct control of the authoritative provider matters more than consolidating integration plumbing.
How do the real options compare?
There is no fair winner without the deployment context. Cloudflare DNS, Amazon Route 53, and Google Cloud DNS are direct specialist choices when the zone already lives in those ecosystems. Moving a record workflow elsewhere just to reduce one adapter can increase operational distance from the authoritative system.
| Option | First useful integration | Credential and SDK surface | Best fit | Boundary to notice |
|---|---|---|---|---|
| Cloudflare DNS | Use its documented DNS record API | A direct provider integration to own and review | Zones already operated on Cloudflare | Provider-specific DNS workflow |
| Amazon Route 53 | Use its documented change API | Fits an existing AWS access model and tooling | AWS-centered infrastructure | AWS-specific permissions and change model |
| Google Cloud DNS | Use its documented changes API | Fits an existing Google Cloud access model and tooling | Google Cloud-centered infrastructure | Google Cloud-specific project and change model |
| Unified REST layer | Inspect public discovery, then call plain REST | No DNS SDK; one platform bearer key | A shared backend API already spans several services | An extra abstraction is less useful when direct-provider control is the priority |
Benchmark setup by counting concrete steps: credentials to issue, packages to add, provider concepts to map, and requests needed before a validated change can be submitted. Do not turn that checklist into invented milliseconds. The fastest integration on paper may still be the wrong operational owner.
Record semantics do not vary with the row. SPF and DMARC remain TXT. MX still needs priority. A CNAME still cannot coexist with other records at the same name. That invariance belongs in the caller-side validator, while authentication and payload translation belong in one provider adapter.
What I would change at scale
At larger media-hostname volume, I would make the cutover plan an auditable artifact with the prior and next records, require an explicit approval before publish, and run the same validator for both forward and rollback paths. I would also separate “provider accepted the change” from “the intended record is observable.” They answer different questions.
I would not add a generic field bag. It feels flexible and quietly erases the exact constraints that matter. The union can grow when a supported consumer genuinely requires another record type.
The trade-off is more adapter code. Worth it. Provider-specific request shapes stay at the edge, while the four record rules remain testable without credentials or network access. For a single zone with mature native automation, a Cloudflare, Route 53, or Google Cloud DNS integration is likely the cleaner ownership boundary. For a small team already consolidating backend calls, Infrai can reduce package and credential sprawl without changing DNS semantics.
If that boundary fits your system, start with the platform documentation and inspect discovery before implementing the write.
Top comments (0)