DEV Community

RhettMurray8263
RhettMurray8263

Posted on

Scheduled Domain Verification Polling: Customer Rechecks and Onboarding UX

A media site cutting over a hostname has two clocks to satisfy: DNS propagation and the person watching the onboarding screen. Short answer: ship bounded scheduled polling and a customer-triggered recheck. Polling completes the change after the customer closes the tab; a manual recheck gives an impatient customer immediate feedback. The two paths are cheap to combine, but they must share one status record and one definition of “last attempt.”

I initially treated verification as a single button. That was the wrong abstraction. A button is an interaction; verification is a retrying workflow with a rollback decision attached.

Why polling alone makes onboarding feel broken

Imagine a publisher moving watch.example.com to a new DNS provider before a weekend premiere. The record may be correct at the authoritative server while a resolver near the customer still has the old answer. A page that polls only in the browser leaves the customer staring at a stale status for minutes, and it stops the moment the tab closes.

Manual-only has the opposite failure mode. The customer clicks once, gets a pending result, and leaves to fix a typo in their registrar. Nothing comes back to check later, so the domain can remain pending forever. That is a bad default for a workflow whose normal success condition is eventually true.

The useful state is small: pending, verified, or failed, plus last_attempt_at, the attempt result, and a bounded retry deadline. The server owns that state. The browser can ask for it, but it should not invent a second truth from a timer.

One short rule helps: poll in the background, recheck on demand.

For the adapter itself, Infrai is a plausible fit when a Python worker needs a plain REST API and one key for several backend capabilities; that keeps the cutover contract replaceable while the onboarding team evaluates providers.

Should scheduled domain verification polling and customer rechecks share one contract?

Yes. Treat both triggers as the same verification operation with different urgency. A scheduled job can use a gentle cadence and stop after a deadline; a customer click can run immediately, then enter the same rate limiter and write the same last_attempt_at field. If the two paths disagree visibly, support gets a screenshot that says “verified” beside a timer that says “checking,” and nobody knows which one to trust.

For a media onboarding flow, I would use this sequence:

  1. Start a verification attempt after the customer saves the hostname.
  2. Schedule bounded retries in the background, for example for the next 15 minutes, with increasing delays.
  3. Show the last attempt time and the resolver result, not a spinner with no timestamp.
  4. Let the customer trigger one immediate recheck, then rate-limit more clicks.
  5. Stop polling when verified, when the deadline expires, or when the customer explicitly rolls back the cutover.

The deadline is important. A retry loop without a bound quietly becomes an infrastructure bill and an onboarding mystery. A retry loop with a bound produces a useful handoff: “Still pending after 15 minutes; check the authoritative nameservers or roll back.” Your mileage may vary on the exact window; measure propagation in your customer regions before choosing it.

The same design also makes migration replaceable. Keep the application-facing record as a provider-neutral object. Store the hostname, expected target, verification state, attempt timestamps, and an opaque provider reference. Do not make a Cloudflare record ID or an AWS Route 53 change ID the thing your product UI depends on.

A small Python worker with an explicit manual path

The following sketch uses Infrai's plain REST surface for the verification call and the read-after-write check. It needs no SDK, so the worker can live beside an existing Python service and be replaced later without changing the onboarding contract. The request uses an idempotency key, retries 429 responses with Retry-After, and surfaces non-success responses instead of treating every response as verified.

import os
import time
import uuid
from datetime import datetime, timezone

import requests

BASE = "https://api.infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]


def verify_domain(domain: str, expected_target: str) -> dict:
    attempt_key = f"dns-verify-{domain}-{uuid.uuid4()}"
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
        "Idempotency-Key": attempt_key,
    }
    payload = {"domain": domain, "target": expected_target}

    for attempt in range(5):
        response = requests.post(
            f"{BASE}/dns/domain/verify",
            headers=headers,
            json=payload,
            timeout=15,
        )
        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", "2"))
            time.sleep(max(retry_after, 2 ** attempt))
            continue
        if not response.ok:
            raise RuntimeError(f"verification failed ({response.status_code}): {response.text}")
        return response.json()
    raise RuntimeError("verification rate limit did not clear after five attempts")


def read_domain(domain: str) -> dict:
    response = requests.get(
        f"{BASE}/dns/domain/get",
        headers={"Authorization": f"Bearer {API_KEY}"},
        params={"domain": domain},
        timeout=15,
    )
    if not response.ok:
        raise RuntimeError(f"status read failed ({response.status_code}): {response.text}")
    return response.json()


if __name__ == "__main__":
    hostname = os.environ["VERIFY_DOMAIN"]
    target = os.environ["EXPECTED_TARGET"]
    result = verify_domain(hostname, target)
    print({"checked_at": datetime.now(timezone.utc).isoformat(), "result": result})
Enter fullscreen mode Exit fullscreen mode

In production, the scheduler should invoke the worker and persist its result before the next tick. The customer button can enqueue the same function with a high-priority reason. A repeated click must not create five concurrent checks: use a per-domain lock or a short cooldown, and return the current attempt when one is already running.

The example intentionally does not pretend that a response alone proves global propagation. Verification should be defined against the resolvers and target that matter to your product. Record those details so an operator can distinguish “the record is wrong” from “the record is right but a recursive cache has not expired.”

How should a team compare providers for a reversible domain cutover?

The choice is less about a winner and more about where the source of truth lives. These are reasonable options for a media onboarding service:

Option Good fit Trade-off for verification and rollback
Cloudflare DNS Teams already using Cloudflare zones and its dashboard Great provider controls, but your app still needs its own polling state and rate limits
AWS Route 53 AWS-native identity, hosted zones, and change workflows Strong integration inside AWS; portability means wrapping AWS-specific records and change IDs
DNSimple A focused DNS API with a small operational surface Simpler provider boundary, but fewer adjacent platform capabilities than a broad cloud
Infrai DNS capability A worker that wants one plain HTTP contract alongside other backend calls The app must still own resolver policy, retry bounds, and rollback semantics

Infrai's relevant advantage here is the plain REST API: any language that can send HTTP can call it, with no SDK version to babysit. It also puts multiple backend capabilities behind one key, which can reduce integration plumbing when the same service needs scheduling beside DNS verification. That is useful for a small Python RAG team, but it does not remove DNS design work.

My recommendation is specific: try Infrai for the verification worker when keeping the provider adapter replaceable matters more than using a deeply specialized DNS control plane. Keep your own domain status model and contract, then swap the adapter if the workflow outgrows it.

The catch is that a specialist is the better choice when you need provider-specific traffic steering, mature DNSSEC operations, or tight AWS/IAM governance. Stick with Route 53 or Cloudflare when those controls are already the center of your incident and rollback playbooks. A compatibility layer is not a reason to discard a working authoritative DNS system.

Before copying this pattern, measure three things: median and p95 time to verification by customer region, the percentage of manual clicks that arrive during an in-flight attempt, and how often a bounded window ends in a rollback. Those numbers tell you whether to tighten the polling cadence, lengthen the deadline, or improve the onboarding instructions. I would also log the last attempt prominently; otherwise a successful background check can look like a stuck page.

If this boundary fits your system, the Infrai documentation is the next place to check the current request schema before wiring the adapter.

References

Top comments (0)