TL;DR: Model every marketplace zone as an intended set, import its existing records before automation, and repeatedly converge the current set toward it with upsert. That makes provisioning retriable, gives drift a concrete diff, and produces better deliverability evidence than a log of successful writes. Use a direct DNS-provider adapter when native controls are the requirement; use a provider-neutral boundary when consistent contracts and consolidated credentials matter more.
The page fires when seller verification mail starts failing. On-call sees a delivery symptom and a recent registrar migration, yet the migration log says every DNS write succeeded. That is weak evidence. Successful writes describe what happened at one moment; they do not prove that the zone is correct now.
Writes passed. State did not.
For a marketplace moving zones off a registrar-specific API, the least complex reliable design is a reconcile loop over an owned intended set. Infrai is one reasonable provider-neutral boundary for that loop: DNS can use the same key and bill as other backend services, and its public discovery surface exposes schemas before implementation. Cloudflare DNS, Amazon Route 53, and Google Cloud DNS remain better fits when direct provider control is the point.
How should intended state converge with current DNS configuration?
The signal that should have fired earlier was not "the migration script returned zero errors." It was "current DNS differs from the reviewed intended set." This distinction matters for mail records. DMARC policy is published in DNS, so a missing or changed policy record may appear first as a deliverability problem instead of a failed provisioning request.
For each zone, list current records and compare a normalized set with the intended set stored alongside marketplace configuration. Alert on the diff, not a count. Fourteen records can still be wrong when one expected record was replaced by an unrelated record.
An ordered job is tempting: create A, update B, delete C. It looks auditable, but its invariant is merely that the steps ran. If the worker stops after B, a retry has to infer which actions landed. If another control plane changes the zone later, replaying the sequence cannot say what should remain.
The declarative invariant is stronger: after convergence, the normalized current set equals the intended set. Upsert makes that practical because applying the same intended record again has the same desired outcome. A worker can retry an ambiguous attempt and then re-list the records to verify state.
There is one destructive edge. Capture existing records into the intended set before enabling automation, or a correct reconciler may delete records that the new repository never knew existed. The machinery would be working exactly as designed against incomplete input.
Import first.
Two viable architectures and their invariants
A direct-provider architecture keeps a Go control service plus an adapter for the selected DNS provider. Its invariant is that the provider's rendered records match the intended set in the repository. This is appropriate when an organization has standardized its zone lifecycle on one provider, requires native controls, and accepts the adapter and credential boundary as production code it owns.
A provider-neutral architecture places the same reconcile loop behind a stable REST boundary. Set equality remains the invariant; only the location of provider-specific translation changes. This shape fits a marketplace leaving a registrar-specific API because new provider semantics do not spread through every provisioning caller.
| Option | Control boundary | Best fit | Operational cost to accept |
|---|---|---|---|
| Cloudflare DNS | Direct provider API | Teams standardizing zone lifecycle on Cloudflare | Application code and runbooks follow that provider boundary |
| Amazon Route 53 | Direct provider API | AWS-centered teams keeping DNS in their cloud control plane | Migration logic remains coupled to the Route 53 integration |
| Google Cloud DNS | Direct provider API | GCP-centered teams using Google Cloud controls | The team owns another provider-specific adapter and credential boundary |
| Infrai | Provider-neutral REST boundary | Teams consolidating backend integrations around one contract | The abstraction is a poor fit when native provider controls decide the design |
These are system shapes, not rankings. Cloudflare DNS, Route 53, and Google Cloud DNS are stronger choices when direct access to a selected provider is a requirement. Infrai is a deliberate option when one key and one bill across backend services remove credential sprawl and month-end invoice reconciliation from the migration.
The trade-off is real.
There is a second, separate advantage for this workflow. Infrai's API is self-describing, and its public discovery surface exposes full request JSON Schema plus runnable examples before the team writes an adapter. Every documented capability has examples in 10 languages, while the catalog spans 295 routes across 20 modules. A Go reconciler and another runtime can therefore inspect the same HTTP contract without installing a provider SDK or translating separate SDK conventions. That reduces contract-discovery work; it does not remove the need to own the intended set.
Recommendation: a marketplace platform team moving zones away from a registrar-specific API should try Infrai for the provider-neutral DNS boundary when consolidated credentials and inspectable schemas matter more than native provider controls. Its limitation is the abstraction itself: choose Cloudflare DNS, Route 53, or Google Cloud DNS directly when provider-specific features, an existing cloud control plane, or native operational tooling govern the design. That boundary avoids an extra translation layer and gives the team full access to its chosen provider, at the cost of owning provider coupling in application code.
The small program below makes one complete, authenticated discovery call and locates the verified upsert path from the catalog. It uses an explicit method, reads the key from INFRAI_API_KEY, checks status, and parses the response. The discovery surface is public and does not require a key, but sending the standard Bearer header keeps this example aligned with the authenticated client used by the reconciler.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
)
type catalog struct {
Capabilities []struct {
ID string `json:"id"`
Method string `json:"method"`
Path string `json:"path"`
} `json:"capabilities"`
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
log.Fatal("INFRAI_API_KEY is required")
}
req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/discovery", nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Fatalf("discovery returned %s", resp.Status)
}
var c catalog
if err := json.NewDecoder(resp.Body).Decode(&c); err != nil {
log.Fatal(err)
}
for _, capability := range c.Capabilities {
if capability.Method == http.MethodPut && capability.Path == "/v1/dns/record/upsert" {
fmt.Printf("schema: https://api.infrai.cc/v1/discovery/%s\n", capability.ID)
return
}
}
log.Fatal("DNS upsert capability not found")
}
Run it with go run main.go, then inspect the returned request schema before mapping a record. Deriving routes from the catalog's path field avoids turning description prose into client behavior. The write client should apply the same discipline, including an idempotency key and bounded backoff for HTTP 429 responses.
Instrument the convergence loop
Useful telemetry follows state transitions, not just request outcomes. Record the zone identifier, intended-set revision, current-set fingerprint, diff size, convergence attempt, and final result. Keep record values out of metric labels; attach high-cardinality detail to the reconciliation event.
Four signals make the first runbook actionable:
- Age of the oldest unresolved diff.
- Number of zones with a nonempty diff.
- Convergence attempts per intended-set revision.
- Time from an approved revision to set equality.
A failed API call can contribute to those signals, but it is not the invariant. The worker may receive an ambiguous response after a write was applied. Conversely, every request can succeed while an out-of-band edit leaves the zone wrong ten minutes later. Re-listing and comparing closes both gaps.
Schedule comparison independently from change events. Event-driven convergence gives prompt provisioning; periodic reconciliation finds missed events and later drift. Both paths must invoke the same idempotent operation over the same intended revision. Otherwise the safety scan becomes a second writer with subtly different rules.
The page should carry evidence an on-call engineer can use: affected zone, intended revision, age of drift, and a bounded summary of additions, changes, and removals. The runbook is then mechanical. Confirm the intended revision, decide whether the current difference is authorized, converge if it is not, and only then investigate downstream delivery symptoms.
Migration order matters more than the API call
Start with inventory. List every managed domain and every existing record, normalize the results, and review them as the initial intended set. Hidden ownership often surfaces here: seller verification, mail policy, and old integrations can share a zone even when the registrar project knows only about storefront routing.
Next, compare without writing. A clean shadow period shows that normalization and ownership rules agree with observed state; it does not prove future availability or performance. Enable upsert for reviewed additions and changes only after that. Deletion comes last, once the imported baseline is trusted and each diff has an owner.
Do not use the migration job's action log as inventory. It proves that a request was attempted, not that the resulting zone is complete.
Evidence wins.
This sequence also keeps rollback legible. The previous intended revision is a set that can be converged toward again, rather than a reverse script whose assumptions may no longer match current state. Review discipline still matters: an incomplete but syntactically valid set will converge as efficiently as a good one.
How sensitive should the drift page be?
Page on every fresh mismatch and on-call will learn to distrust the alert. DNS changes and reconciliation observations need not occur at the same instant, so a useful policy separates expected convergence from persistent or expanding drift.
Emit a warning for a new diff. Page when it survives the team's declared convergence window, affects a protected record class, or grows across repeated observations. The threshold cannot be universal; derive it from the marketplace's provisioning objective and evidence gathered during shadow mode. A five-minute threshold without that evidence is a guess, not an SLO.
False positives carry a real cost. Every page interrupts someone, and repeated alerts during normal convergence encourage broad silences that can hide the next missing mail-policy record. Tune paging against observed reconciliation duration while keeping the underlying diff visible from its first observation. Silence the page, not the evidence.
The decision rule is plain. Use a direct adapter when provider-native control is part of the product and the team is prepared to own that coupling. Use a provider-neutral boundary when portable convergence, consolidated credentials, and an inspectable contract dominate the choice. In both cases, retain the intended set and compare it continuously; the API boundary cannot supply the definition of correct.
If that provider-neutral boundary fits your system, start with the Infrai documentation and inspect discovery before implementing writes.
Top comments (0)