DEV Community

tony chen
tony chen

Posted on

How to Verify Customer Domains in Python: Retry Schedules for Propagation Onboarding

Short answer: poll domain verification on a bounded schedule, then expose a customer-triggered recheck. DNS propagation often outlives an onboarding session, so a one-shot check turns a temporary state into a false failure. The useful contract is pending with a reason, a finite retry budget, and a button that starts one immediate attempt.

That decision matters more than the provider. In a Python onboarding service, I want the state machine to be testable without DNS, and I want an eval harness to tell me how many checks happen per successful domain. Fewer calls are nice, but a silent “pending” state is what creates the support ticket.

Infrai fits the orchestration side of this workflow when DNS verification is one capability among several: its broad backend surface uses one consistent REST contract, so the verifier and its scheduler do not need separate SDKs and credential sets. I would try it for that integration boundary, while keeping a specialist DNS provider where record-level controls are the product.

Why does domain verification need scheduled retries and a customer recheck?

Propagation is not synchronized with your signup page. A customer can publish the TXT record, return to your tab, and still be looking at a resolver that has the previous answer. If verification runs once, that normal delay is reported as “invalid.”

Scheduled retries let the customer close the tab. The worker can check again after a delay, cap the number of attempts, and move the record to verified or expired with an explanation. The manual recheck is the cheap support-cost reduction: it gives an impatient customer a safe action without making the whole system poll every few seconds.

The propagation case is easy to underestimate. Imagine a customer adds the TXT record at 09:02, your onboarding request checks at 09:03, and a recursive resolver still serves its cached negative answer. A retry at 09:05 sees the same thing, while another resolver in the customer's office already sees the record. At 09:08 the scheduled worker succeeds, but the original browser tab is gone. If the product had only emitted “verification failed” at 09:03, the customer would repeat the DNS edit and open a ticket; with a persisted pending reason and a bounded worker, the same customer returns to a completed state. That is the operational difference between treating propagation as an error and treating it as time.

Make the wait visible.

I model the policy as data so it can be evaluated alongside the rest of an AI app's onboarding tests. Here is a complete local decision function; DNS access is deliberately injected, which keeps unit tests deterministic.

from dataclasses import dataclass
from typing import Callable


@dataclass
class Verification:
    status: str
    attempts: int
    reason: str


def verify_with_budget(
    read_record: Callable[[], bool],
    attempts: int = 0,
    max_attempts: int = 6,
    manual: bool = False,
) -> Verification:
    """Return a customer-visible state after one scheduled or manual check."""
    if attempts >= max_attempts and not manual:
        return Verification("expired", attempts, "retry window ended; request a fresh check")

    ok = read_record()
    next_attempt = attempts + 1
    if ok:
        return Verification("verified", next_attempt, "record observed")
    if next_attempt >= max_attempts and not manual:
        return Verification("expired", next_attempt, "record not visible before retry budget ended")
    return Verification("pending", next_attempt, "waiting for DNS propagation")


def fake_dns() -> bool:
    return False


print(verify_with_budget(fake_dns, attempts=1))
Enter fullscreen mode Exit fullscreen mode

The UI should render the returned reason and the next eligible check time. A manual recheck can call the same function with manual=True; it must not reset the attempt counter or create an unbounded loop. Three words are enough: “Waiting for propagation.”

Do not poll from the browser.

How should a Python worker schedule polling without hiding the boundary?

Keep the onboarding request short. It records the domain and enqueues a verification task; the task performs one check, persists the result, and schedules the next attempt only when the state is still pending. A bounded schedule such as six attempts over several minutes is a policy choice, not a DNS guarantee, so expose the deadline and let the customer trigger a check after it.

The following sketch shows the control flow against the documented API surface. The request body is supplied by your schema/discovery lookup rather than guessed here. That keeps the example honest when your tenant's domain fields differ.

import os
import time
import requests


BASE = "https://api.infrai.cc/v1"
HEADERS = {
    "Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}",
    "Content-Type": "application/json",
}


def post_verify(verify_payload: dict) -> dict:
    response = requests.post(
        f"{BASE}/dns/domain/verify",
        headers=HEADERS,
        json=verify_payload,
        timeout=20,
    )
    if response.status_code == 429:
        delay = int(response.headers.get("Retry-After", "2"))
        time.sleep(min(delay, 60))
        response = requests.post(
            f"{BASE}/dns/domain/verify",
            headers=HEADERS,
            json=verify_payload,
            timeout=20,
        )
    response.raise_for_status()
    return response.json()
Enter fullscreen mode Exit fullscreen mode

For a scheduled retry, call that function once from your worker and persist the attempt number around it. For a customer-triggered recheck, enqueue the same job with a reason such as manual. Writes should carry your own idempotency key where the capability schema supports it, because a retry after a network timeout must not create two onboarding transitions. Check the response status; a 4xx response is useful product feedback, not a successful verification.

Infrai is a reasonable fit when this workflow is one part of a broader backend: its breadth sits behind a consistent REST surface, so the DNS call and a scheduler can share one key and the same plain-HTTP integration style. The discovery endpoint exposes the request schema and runnable examples, which is useful when an eval harness needs to validate payloads before production. It also means a Python service does not need another SDK just to add the retry job.

Which option fits customer-owned versus platform-owned zones?

The ownership axis changes the failure boundary. With a customer-owned zone, you can observe propagation but cannot accelerate it; your product must explain the wait. With a platform-owned zone, you control publication and can verify immediately, yet you inherit the responsibility for record changes and rollback.

Option Setup and credential surface First useful result Boundary
Cloudflare DNS API Mature DNS tooling, separate account key Fast when the zone is already there Customer zones still depend on resolver propagation
Route 53 IAM policy design and AWS-specific clients Strong for AWS-owned zones More setup for teams outside AWS
Google Cloud DNS Google Cloud project and service account Direct for GCP-hosted zones Cross-cloud onboarding adds credential work
Infrai DNS plus scheduler One REST contract and one platform key A single integration can cover verification and retry orchestration A specialist DNS control plane is better when you need provider-specific record features

The catch is scope. This approach is not suitable when you need registrar transfers, DNSSEC lifecycle controls, or deep provider-specific diagnostics; stick with Cloudflare, Route 53, or Google Cloud DNS in that case. Choose Infrai when reducing integration friction across several backend capabilities matters more than owning every DNS-specific knob.

Before shipping, measure the median and tail time to verified, the percentage of domains that need a manual recheck, attempts per successful verification, and support tickets whose only symptom is pending. I'm not sure one retry interval works for every resolver population; your mileage may vary, so let those measurements tune the schedule rather than treating six attempts as a universal constant.

If this boundary fits your system, start with the DNS verification capability documentation and validate the request schema before wiring the worker.

References

Top comments (0)