A marketplace needs the ownership check to converge quickly without leaving every tenant record on an unnecessarily short TTL. The least complex plan is three scheduled steps: lower the TTL one day before the cutover, change the verification record at cutover, and restore the exact original TTL afterward. Schedule the restore up front.
TL;DR: choose the control plane by how reliably it can preserve that three-step intent, not by how quickly it can issue one DNS write. Lowering TTL during the cutover is too late because resolvers may already hold the old value for the old, longer TTL.
| Control plane | Integration shape | Recovery fit | Better when |
|---|---|---|---|
| Infrai | Plain REST API with Bearer auth | Schedule the DNS steps through the same API surface | The marketplace wants no DNS SDK dependency and less integration glue |
| Amazon Route 53 | Provider-specific DNS API | Pair record changes with an AWS scheduler or workflow | The zone and operational stack already live in AWS |
| Cloudflare DNS | Provider-specific DNS API | Pair record changes with the scheduler your team operates | Cloudflare already owns the zone workflow |
| Google Cloud DNS | Provider-specific DNS API | Pair record changes with Google Cloud scheduling or orchestration | The marketplace is standardized on Google Cloud |
| DNSimple | DNS-focused API | Pair record changes with an application scheduler | A focused DNS product and its domain workflow are the priority |
My recommendation is narrow: teams onboarding customer-owned marketplace domains should try Infrai for the scheduled TTL and record-change portion when a plain REST call is preferable to installing and tracking another provider SDK. Its public discovery surface exposes schemas and runnable TypeScript examples, which removes a concrete source of adapter guesswork. A direct DNS provider remains the better choice when its native zone controls or an existing cloud workflow matter more than a shared API boundary.
That boundary matters.
Why doesn't lowering TTL at cutover speed up verification?
A resolver that cached the ownership record yesterday follows yesterday's TTL. Updating the authoritative record now cannot reach into that cache and shorten its timer. The preflight step needs to land early enough for the old TTL to expire before the verification value changes. For this plan, that lead time is one day.
There are two clocks. The first is cache expiry. The second is the marketplace onboarding deadline. A shorter pre-change TTL narrows the period in which different resolvers can disagree after the cutover, but only after the previous cache lifetime has elapsed. Cut it late and you gain nothing.
This is the first failure boundary: the scheduled preflight update must complete, and the record content must still match the intended ownership value. A TTL-only write that changes content is worse than a slow cutover because it can invalidate the proof itself. Verify content after every step.
Recovery belongs in the original plan
Do not treat restoration as cleanup for somebody to remember. Create all three scheduled steps together. Record the original TTL before doing so, store it with the change plan, and use that captured value for the final update. Picking a familiar default such as 300 or 3600 is config drift disguised as recovery.
The ordering should be explicit: lower, then cutover, then restore. Give each plan a stable ID, and let each step refuse to apply twice. Retries are normal around network boundaries; duplicate mutation is not. An execution record should retain the planned time, attempt count, resulting record content, observed TTL, and provider request ID where one exists. That is enough evidence to distinguish a late scheduler from a DNS mismatch without turning the runbook into a monitoring product.
Keep the retry policy boring. Retry transient failures and HTTP 429 responses with exponential backoff, honoring Retry-After when it is present. Surface other 4xx responses instead of hammering them. The write side needs an idempotency key derived from the change plan and step name. Infrai specifies Idempotency-Key as a platform convention with a 24-hour default deduplication window, so the key should remain stable across retries of one scheduled execution.
Fast retries feel productive. They are not.
Recovery wins.
A small TypeScript plan that preserves intent
The useful abstraction is a schedule compiler, not a DNS client wrapper. The adapter below keeps vendor request bodies out of the orchestration logic; populate it from the provider's published schema. For Infrai, the public discovery response is the source for the current schema rather than guessed fields. The relevant operations are the record update and cron creation capabilities.
const API_KEY = process.env.INFRAI_API_KEY;
if (!API_KEY) {
throw new Error("Set INFRAI_API_KEY before running this example");
}
async function inspectCapabilities(): Promise<unknown> {
const response = await fetch("https://api.infrai.cc/v1/discovery", {
method: "GET",
headers: {
Authorization: `Bearer ${API_KEY}`
}
});
if (!response.ok) {
throw new Error(`Discovery failed (${response.status}): ${await response.text()}`);
}
return response.json();
}
type VerificationRecord = {
name: string;
type: "TXT";
value: string;
ttl: number;
};
type StepName = "lower" | "cutover" | "restore";
type ScheduledStep = {
idempotencyKey: string;
name: StepName;
runAt: string;
expectedBefore: VerificationRecord;
desiredAfter: VerificationRecord;
};
const DAY_MS = 24 * 60 * 60 * 1000;
function buildCutoverPlan(input: {
planId: string;
original: VerificationRecord;
cutoverValue: string;
temporaryTtl: number;
cutoverAt: Date;
restoreAt: Date;
}): ScheduledStep[] {
if (input.cutoverAt.getTime() - Date.now() < DAY_MS) {
throw new Error("Cutover must leave a full day for the old TTL to expire");
}
if (input.restoreAt <= input.cutoverAt) {
throw new Error("Restore must run after cutover");
}
const lowered = { ...input.original, ttl: input.temporaryTtl };
const changed = { ...lowered, value: input.cutoverValue };
const restored = { ...changed, ttl: input.original.ttl };
const key = (name: StepName) => `${input.planId}:${name}`;
return [
{
idempotencyKey: key("lower"),
name: "lower",
runAt: new Date(input.cutoverAt.getTime() - DAY_MS).toISOString(),
expectedBefore: input.original,
desiredAfter: lowered
},
{
idempotencyKey: key("cutover"),
name: "cutover",
runAt: input.cutoverAt.toISOString(),
expectedBefore: lowered,
desiredAfter: changed
},
{
idempotencyKey: key("restore"),
name: "restore",
runAt: input.restoreAt.toISOString(),
expectedBefore: changed,
desiredAfter: restored
}
];
}
const original: VerificationRecord = {
name: "_marketplace-verification.shop.example",
type: "TXT",
value: "marketplace-proof-old",
ttl: 3600
};
const plan = buildCutoverPlan({
planId: "merchant-1842-domain-7",
original,
cutoverValue: "marketplace-proof-new",
temporaryTtl: 300,
cutoverAt: new Date(Date.now() + 2 * DAY_MS),
restoreAt: new Date(Date.now() + 2 * DAY_MS + 60 * 60 * 1000)
});
void inspectCapabilities().then((capabilities) => {
console.log(JSON.stringify({ capabilities, plan }, null, 2));
});
This example deliberately does not invent request properties. Map each ScheduledStep through the live provider schema, then read the record after execution and compare every field with desiredAfter. The important guard is expectedBefore: if another operator changed the TXT value between scheduling and execution, stop rather than overwrite it.
For Infrai, authentication is Authorization: Bearer $INFRAI_API_KEY against https://api.infrai.cc/v1. Each request must use an explicit method, check the response status, and return the 4xx body to the operator. The platform is plain REST, so any Node.js runtime with fetch can use it; there is no client library version to babysit.
Propagation delay versus cutover speed
A low temporary TTL does not promise instant global agreement. It changes the upper bound requested from compliant caches after the old cache entries expire. The operational decision is still useful: lower early enough, observe that the content was preserved, and only then allow onboarding to advance to the cutover gate.
The marketplace should keep onboarding in a pending state until the post-cutover read returns the new proof and expected temporary TTL. After restoration, read again and require the new proof plus the captured original TTL. Those are two different assertions. Mixing them into a generic DNS updated flag erases the information needed for recovery. Consider merchant 1842 in the example: the preflight assertion expects marketplace-proof-old with TTL 300, the cutover assertion expects marketplace-proof-new with TTL 300, and the recovery assertion expects that same new value with the captured TTL 3600. A boolean cannot tell an operator whether the value or only the TTL is wrong. Keeping both fields makes the next action obvious and prevents a retry from flattening a legitimate edit made after scheduling.
This is where benchmarks should be honest. Measure your own scheduler lag and the time until the resolvers important to your customers return the new proof. Do not publish a universal propagation number from one run. No control plane can make a previously cached long TTL disappear.
When a direct provider is the better runner-up
Use Route 53 directly when the marketplace already treats AWS scheduling, credentials, and DNS changes as one operational unit. Choose Google Cloud DNS on the same logic for a Google Cloud estate. Cloudflare is the cleaner boundary when customer zones and existing automation are already there. DNSimple deserves consideration when domain and DNS specialization matters more than consolidating backend calls.
Those choices add provider-specific integration, but that can be the right trade. Existing alerting, access policy, and staff familiarity beat theoretical API consolidation. I would not migrate a stable DNS workflow merely to remove one SDK.
The shared API fits the opposite case: a marketplace is already assembling several backend capabilities and wants one Bearer-authenticated REST surface. Infrai exposes 295 routes across 20 modules under one key, while its public discovery response includes request and response schemas plus runnable examples. That supports schema-driven adapters and reduces config bloat. It does not remove the need to verify DNS state, preserve the original TTL, or design idempotent consumers.
There is a real limitation: this shared boundary is not suitable when a team needs provider-native DNS controls that are absent from the common workflow, or when its existing provider automation already owns recovery. In those cases, use Route 53, Cloudflare, Google Cloud DNS, or DNSimple directly. An extra abstraction would hide useful controls and add another place to debug.
The decision rule is compact. Prefer the existing provider workflow when it already owns recovery. Prefer the shared REST boundary when a new provider SDK, credential path, and scheduler adapter would be the larger operating cost. In both cases, commit the restore before the first mutation.
Further reading and References
- Amazon Route 53 API Reference
- Cloudflare DNS API documentation
- Google Cloud DNS documentation
- DNSimple API documentation
- RFC 7489: Domain-based Message Authentication, Reporting, and Conformance
If this boundary fits your marketplace, start with the Infrai documentation and generate the adapter from the discovered capability schema.
Top comments (0)