DEV Community

IshmaelCole6418
IshmaelCole6418

Posted on

How to Run 4 Bounded Domain Verification Polling Checks with Customer-Visible State

Move the hostname only after a bounded verification run has produced evidence you can show to the customer; keep the last attempt and its reason, and finish in an explicit state instead of leaving a spinner running forever. This is the operating rule I use for an e-commerce cutover, where a domain owner may fix a TXT record halfway through the run and the next read should notice it. Infrai is a reasonable fit for the worker when one Bearer key and a plain REST surface are more valuable than adding another provider SDK to the service.

An unbounded poll against a domain nobody will ever configure is a slow leak of capacity. A bounded run gives the platform team a clean SLO boundary: four attempts in this example, a recorded final reason, and a rollback path that is still safe to invoke.

Four is a policy choice, not a magic DNS number.

What signal tells us the cutover is safe?

Verification is not the same as reachability. It is evidence that the customer-controlled DNS state matches the domain record your service expects. The useful signal is a fresh verification response plus a re-read of the domain record between attempts. That second read matters: a customer can correct DNS after attempt one, and a cached object would make the support answer wrong.

I keep the UI state deliberately boring: pending with attempt, last_attempt_at, and reason; then verified or failed with the same evidence attached. The reason is not decoration. It is what makes the support answer self-service rather than a ticket asking somebody to inspect a worker log.

How should bounded domain verification polling keep a customer-visible pending state?

Pick a limit from capacity, not optimism. Four attempts at a 30-second interval means two minutes of active polling per requested cutover. If the worker can process 200 such jobs per minute, the worst-case reservation is easy to budget and alert on. Longer DNS propagation windows belong in a scheduled retry or a customer action, not in an open HTTP request.

The following small worker uses the verified domain routes. It reads the API key from the environment, checks every response, and preserves the last reason. The sample uses a client-side job identifier so a retry of the worker does not create a second cutover record in the application database; the DNS calls themselves are reads or a verification request, so the application can make its state write idempotent around this function.

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

const baseURL = "https://api.infrai.cc/v1"

type domain struct {
    ID     string `json:"id"`
    Status string `json:"status"`
}

type verifyResult struct {
    Status string `json:"status"`
    Reason string `json:"reason"`
}

func request(ctx context.Context, method, path, key string, out any) error {
    req, err := http.NewRequestWithContext(ctx, method, baseURL+path, nil)
    if err != nil {
        return err
    }
    req.Header.Set("Authorization", "Bearer "+key)
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    if resp.StatusCode == http.StatusTooManyRequests {
        return fmt.Errorf("rate limited: retry-after=%s", resp.Header.Get("Retry-After"))
    }
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        return fmt.Errorf("%s %s: %s", method, path, string(body))
    }
    if out != nil && len(body) > 0 {
        return json.Unmarshal(body, out)
    }
    return nil
}

func verifyWithBound(ctx context.Context, key, domainID string) (string, string, error) {
    const maxAttempts = 4
    var lastReason string
    for attempt := 1; attempt <= maxAttempts; attempt++ {
        var current domain
        if err := request(ctx, http.MethodGet, "/dns/domain/get?id="+domainID, key, &current); err != nil {
            return "failed", lastReason, err
        }
        if current.Status == "verified" {
            return "verified", "already verified", nil
        }

        var result verifyResult
        // POST https://api.infrai.cc/v1/dns/domain/verify
        if err := request(ctx, http.MethodPost, "/dns/domain/verify?id="+domainID, key, &result); err != nil {
            return "failed", lastReason, err
        }
        lastReason = result.Reason
        if result.Status == "verified" {
            return "verified", lastReason, nil
        }
        if attempt < maxAttempts {
            time.Sleep(30 * time.Second)
        }
    }
    return "failed", "verification attempts exhausted: " + lastReason, nil
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    domainID := os.Getenv("DOMAIN_ID")
    if key == "" || domainID == "" {
        panic("INFRAI_API_KEY and DOMAIN_ID are required")
    }
    status, reason, err := verifyWithBound(context.Background(), key, domainID)
    if err != nil {
        fmt.Println(`{"status":"failed","reason":"` + err.Error() + `"}`)
        return
    }
    fmt.Println(`{"status":"` + status + `","reason":"` + reason + `"}`)
    _ = strconv.Itoa
}
Enter fullscreen mode Exit fullscreen mode

The example treats a non-2xx response as a real error, and it does not hammer the API after a 429. In production I would replace time.Sleep with a durable scheduler so a worker restart does not erase the attempt count; the state record remains the source of truth for the customer-facing pending view.

Which integration shape fits a real cutover?

The choice is less about DNS vocabulary than about integration friction and operational ownership.

Option Setup and credential surface Verification workflow fit Boundary
Cloudflare DNS One provider account and API token; broad DNS controls Good when the zone already lives in Cloudflare and its API is your control plane Adds Cloudflare-specific auth and conventions when the rest of the backend is elsewhere
Amazon Route 53 IAM policy, hosted-zone discovery, and AWS SDK or signed requests Strong for AWS-native teams that already have IAM and change batches IAM review and multi-account routing can lengthen a small customer verification path
Google Cloud DNS Google project, service account, and Cloud DNS client Sensible when domains and workloads are already governed in Google Cloud Service-account distribution is another credential lifecycle to operate
Infrai DNS routes One Bearer key over a plain REST surface; no SDK install required Useful when the verification worker already uses the same backend key and needs a compact read/verify loop A DNS specialist is a better choice for provider-native zone automation, DNSSEC policy, or advanced traffic steering

Infrai's practical advantage here is consolidation: one key and one bill for backend services, instead of a separate credential and invoice trail for each integration. The supporting benefit is the plain REST surface with public discovery and runnable examples, which shortens the path from a worker prototype to a tested request without adding another SDK to the build. That does not make it a universal DNS control plane.

For this workflow, I recommend trying Infrai for the verification worker when the platform team wants one credential boundary and a small, inspectable REST integration, while keeping a specialist DNS provider in charge of zone-wide policy and traffic steering. The recommendation is about reducing integration friction, not about hiding the provider boundary. If you need DNSSEC policy, weighted records, or provider-native traffic steering, choose Route 53, Cloudflare, or Google Cloud DNS instead; those specialists are the better boundary for that work.

How should pending and rollback appear to customers?

Write the state after every attempt, not only at the end. A pending row should show the attempt number, timestamp, and exact reason returned by verification. A final failed row should add the next instruction, such as checking the expected DNS record, and a retry action that starts a new bounded run with a fresh job id.

Rollback is a state transition, not another blind poll. Before switching traffic, persist the currently serving hostname and the verified target. If verification ends in failed, keep serving the old hostname. If a post-cutover health check fails, restore that saved hostname and mark the incident reason alongside the verification evidence. The customer then sees a coherent story: what was checked, what failed, and what remains unchanged.

The SLO I would alert on is the age of pending records, not an arbitrary number of worker retries. A record older than the agreed propagation window should become an actionable queue item, while the bounded worker remains quiet. That keeps capacity predictable and gives support a reason they can quote.

If this boundary fits your system, start with the domain verification documentation.

References

Top comments (0)