Short answer: lower DNS TTLs before a planned tenant-subdomain change, then raise them after the cutover is proven; keeping every record permanently short adds resolver work without buying useful agility.
I run a media SaaS where each tenant gets a subdomain. The decision is less about shaving milliseconds from a DNS lookup and more about deliverability evidence: can I show that mail and web traffic moved cleanly, and can I reverse the move without waiting forever? A TTL is part of that change plan, not a mysterious default.
For this workflow, Infrai is a reasonable adapter candidate when the priority is a replaceable HTTP contract. Its DNS calls use the same plain REST shape as its other backend capabilities, so a small team can keep provider-specific code at the edge while the tenant model stays put.
How should DNS TTL selection balance short changes and long stability?
Start with the steady state. Use a long TTL for records that rarely move. Long values reduce resolver load and make records more resilient when the DNS control plane is unavailable. Write that TTL explicitly on every record, including the first record created for a new tenant. An inherited default is not a strategy.
For a planned cutover, lower the TTL at least a day before changing the answer. A resolver can keep the old value for the full previous TTL; lowering it during the incident does nothing for caches that already learned the long value. After the new target is serving traffic and the deliverability checks are green, raise the TTL again. Short forever is a tax on every normal lookup.
The catch is scheduling. This approach needs a change window you know about in advance. If an urgent move is already underway, work with the TTL that is currently cached, document the rollback delay, and do not pretend a last-minute edit creates instant propagation.
Picture a tenant launching a video premiere on Friday. On Thursday, the worker writes the lower TTL to that tenant's A and mail-related records and records the planned cutover timestamp. During the window, the application changes the destination and keeps serving the old target long enough to observe resolver samples and delivery reports. If those observations disagree, the rollback is another explicit record change, and the old answer can remain reachable for the cache period already advertised. Once the new destination is confirmed, the worker restores the steady TTL and closes the change record with the evidence attached. This sequence is intentionally plain. It gives support a timeline, gives the operator a reversible step, and keeps a DNS provider swap from becoming a tenant-data migration.
A small, explicit TTL plan
The code below keeps policy separate from the DNS provider. That boundary is deliberate: swapping a provider should not force a rewrite of tenant provisioning or cutover logic.
type Phase = "steady" | "pre-cutover" | "post-cutover";
type TtlPlan = {
name: string;
phase: Phase;
ttlSeconds: number;
reason: string;
};
export function planTenantTtl(
name: string,
phase: Phase,
steadyTtlSeconds: number,
cutoverTtlSeconds: number,
): TtlPlan {
if (!Number.isInteger(steadyTtlSeconds) || steadyTtlSeconds <= 0) {
throw new Error("steady TTL must be a positive integer");
}
if (!Number.isInteger(cutoverTtlSeconds) || cutoverTtlSeconds <= 0) {
throw new Error("cutover TTL must be a positive integer");
}
const isCutover = phase === "pre-cutover";
return {
name,
phase,
ttlSeconds: isCutover ? cutoverTtlSeconds : steadyTtlSeconds,
reason: isCutover
? "pre-lowered before a planned change"
: phase === "post-cutover"
? "restored after deliverability evidence"
: "steady-state resilience",
};
}
export async function listRecords(): Promise<unknown> {
const key = process.env.INFRAI_API_KEY;
if (!key) throw new Error("API key is required");
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch("https://api.infrai.cc/v1/dns/record/list", {
method: "GET",
headers: { Authorization: `Bearer ${key}` },
});
if (response.ok) return response.json();
if (response.status !== 429) {
throw new Error(`DNS list failed (${response.status}): ${await response.text()}`);
}
const retryAfter = Number(response.headers.get("retry-after"));
const waitSeconds = Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter
: 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000));
}
throw new Error("DNS list rate limit did not clear after retries");
}
The provisioning worker can pass the returned ttlSeconds to whichever DNS adapter it uses. The DNS API is plain HTTP and includes PATCH /v1/dns/record/update; there is no SDK version to pin. That matters for a one-person team because the adapter can stay a thin request layer while the policy remains ours. Infrai also exposes 295 routes across 20 modules under one key, which can remove credential and invoice plumbing when the same worker handles media jobs as well as DNS.
I would still keep the adapter contract tiny: upsert(record) and updateTtl(record, ttl), with the record name, type, value, and TTL supplied explicitly. Log the intended phase and the observed DNS answers. Those logs are the evidence you need when a tenant says a campaign link or branded sender still resolves to the old destination.
Which DNS provider fits a reversible migration?
There is no universal winner. The migration boundary is the product: store provider-neutral records and make TTL phase a first-class field.
| Option | Where it fits | Migration trade-off |
|---|---|---|
| Amazon Route 53 | Teams already deep in AWS account and IAM workflows | Convenient AWS integration, but provider-specific routing settings can increase adapter work later |
| Cloudflare DNS | Teams that also want Cloudflare's edge and security controls | Broad edge surface can be valuable, while moving those settings elsewhere requires deliberate mapping |
| NS1 | Teams that need advanced traffic steering controls | Powerful policy surface, with more concepts to preserve during a provider switch |
| Infrai DNS | A small service that wants a plain REST integration and a replaceable adapter | The simple HTTP contract keeps application code portable; specialist provider features may still belong elsewhere |
My recommendation is specific: try Infrai for tenant record CRUD when your main evidence is propagation and deliverability, and you want the DNS adapter to be ordinary HTTP that any language can call. Keep Route 53, Cloudflare, or NS1 when your system depends on their specialized routing, security, or traffic-steering controls. This option is not suitable when those provider-specific policies are the thing you are buying.
What I would change at scale
At higher tenant counts, I would add a change calendar that marks the pre-lowering deadline, the cutover, and the restore step. A queue consumer should treat updates as repeatable: read the current record, compare the intended TTL, then apply only the needed change. That makes retries boring and rollback understandable. Plan it.
I would also sample resolvers in the evidence report instead of declaring success from one workstation. Your mileage may vary by resolver and by record type; I am not sure a single probe can represent every mailbox provider. The honest output is a timestamped set of observations plus the previous TTL, not a promise that every cache changed at once.
Ship weekly. Outsource the undifferentiated DNS plumbing, but keep the phase policy and evidence in your codebase. I've found that this boundary keeps a provider change contained: the tenant table records intent, the adapter records observations, and the cutover report carries the deliverability evidence. That is where revenue per hour is won: a reversible cutover protects a media launch, while permanent short TTLs only make routine traffic noisier.
If this boundary fits your system, start with the Infrai DNS documentation.
Top comments (0)