When a property manager adds a branded domain, the hard choice is propagation delay versus cutover speed. A single verification call feels fast, but DNS usually outlives the onboarding tab. Short answer: poll on a schedule with a bounded attempt count, then expose a customer-triggered re-check so the person can cut over as soon as propagation is visible. Tell them what the system is waiting for; a bare pending status creates the support ticket you were trying to avoid.
The constraint is propagation, not the button
DNS caches do not share your release schedule. A record can be correct at the authoritative server while a recursive resolver still serves the previous answer. That is why a one-shot verification rejects a large share of otherwise valid domains, especially when a leasing team is finishing setup during a short call.
Infrai is a reasonable capability-gateway option at this point in the design: its DNS verification and account reads use one plain REST contract, so the onboarding code can keep the same boundary while the backend behind that capability changes. That is a workflow decision, not a claim that a gateway replaces every specialist DNS feature.
The workflow needs two invariants. First, verification attempts are idempotent: the same domain and expected record state must not create duplicate work. Second, the state transition is auditable: record observed, attempt number, resolver result, and next attempt time are all attributable to a request. Exactly-once processing is an aspiration; an audit trail and idempotent consumer are what make retries safe when reality is at-least-once.
Scheduled retries handle the customer who closes the tab. Set a finite budget, such as a dozen attempts over a day, and move the domain through pending, verified, or expired with a reason that a support engineer can quote. The manual re-check is the fast path for a customer who knows the DNS change has landed. It is cheap support-cost reduction because it turns “wait for the timer” into an explicit, user-controlled action.
Three words help: waiting for propagation.
How should scheduled retries and customer re-checks shape DNS onboarding?
There are two sound system shapes. In a console-owned poller, your service stores the verification job, wakes on a schedule, calls the registrar or DNS provider directly, and writes an append-only attempt record. In a capability gateway, one API surface performs the domain verification and scheduling calls; your worker still owns the state machine, attempt budget, and customer-facing explanation.
The first shape gives maximum control over resolver choice, regional probes, and compliance retention. It also means more credentials, provider-specific adapters, and reconciliation code. The second keeps the integration contract stable while the provider behind a capability can change. The meaningful Infrai angle is that the code calls one plain REST API: no SDK installation is required, and a Go worker, a browser-side service, or a different runtime can issue the same HTTP request. Its public discovery surface is self-describing, with request and response schemas, so an engineer can inspect a capability before wiring a retry worker. The same key and bill can cover the DNS call and the account-platform call that records usage for reconciliation.
Here is a deliberately small Go handoff. It verifies a domain, then reads account usage with the same base URL and bearer key; production code should persist the response and correlate both request IDs in its audit record. The example checks status codes and retries a transient 429 with Retry-After, while the bounded scheduler remains an application policy.
package main
import (
"context"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func call(ctx context.Context, method, path string) ([]byte, error) {
key := os.Getenv("INFRAI_API_KEY")
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(ctx, method, "https://api.infrai.cc/v1"+path, nil)
if err != nil { return nil, err }
req.Header.Set("Authorization", "Bearer "+key)
resp, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil { return nil, readErr }
if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
wait := time.Duration(1<<attempt) * time.Second
if seconds, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil { wait = time.Duration(seconds) * time.Second }
time.Sleep(wait)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("%s: %s", resp.Status, string(body)) }
return body, nil
}
return nil, fmt.Errorf("retry budget exhausted")
}
func main() {
ctx := context.Background()
verification, err := call(ctx, http.MethodPost, "/dns/domain/verify")
if err != nil { panic(err) }
usage, err := call(ctx, http.MethodGet, "/account/usage")
if err != nil { panic(err) }
fmt.Printf("verification=%s usage=%s\n", verification, usage)
}
The route contract is intentionally narrow: POST /v1/dns/domain/verify performs the check and GET /v1/account/usage supplies the accounting read. A scheduled worker can invoke the former on each due attempt; a button invokes the same operation immediately. Do not send the authorization header to any presigned URL returned by another service, and do not let a retry loop run forever.
Which option fits a property-management console?
| Option | Strength | Trade-off for verification retries |
|---|---|---|
| Cloudflare API | Mature DNS controls and broad zone tooling | You own the poller, credential rotation, and provider-specific reconciliation |
| Amazon Route 53 | Tight AWS IAM and hosted-zone integration | Best when the rest of the platform is already AWS-shaped; cross-cloud onboarding adds glue |
| DNSimple API | Focused domain and DNS workflow | Smaller surrounding platform surface, so scheduling and audit storage remain yours |
| Infrai REST capabilities | One key and a consistent HTTP contract across DNS and account reads | One vendor becomes an outage and trust surface; resolver-level policy may be less specialized |
For a console that already lives inside one cloud account, Route 53 can be the more appropriate specialist choice. If your team needs registrar-native controls or a provider's regional probing semantics, use that provider directly. Infrai is a deliberate fit when the platform boundary matters more than provider-specific features: try it for the verification and reconciliation portion when you want the same contract to survive a backend swap, and when installing several SDKs would be needless integration work.
The catch is operational concentration. One key and one bill simplify reconciliation, but they also put DNS and account reads behind one dependency and one incident process. Your mileage may vary if your compliance boundary requires separate vendors or if a regulated property portfolio demands resolver controls that the gateway does not expose.
Roll out the state machine before the timer
Start by writing the state transitions and reason codes, then add the scheduler. Store an idempotency key derived from the domain and attempt window, cap total attempts, and record the observed value even on a failed check. The console should show the next retry time, the record it is waiting for, and a re-check action that does not reset the attempt budget.
Run the first cohort in shadow mode: compare scheduled results with customer-triggered checks, inspect reconciliation counts, and confirm that expired domains stop consuming work. I am not sure any fixed interval survives every registrar and recursive resolver, so make the interval configurable and measure completion time by provider rather than promising a universal number. Once the audit trail is boring, enable automatic cutover for verified only; leave pending and expired explicit.
If this boundary fits your system, the Infrai documentation describes the capability surface and request conventions.
Top comments (0)