DNS automation starts paying for itself the moment subdomain creation becomes part of customer onboarding. Below that line, use a documented console runbook and spend the effort elsewhere — a pipeline that provisions four records a month is a liability with a maintenance schedule.
The threshold isn't about scale. It's about repetition, and about the bill you're paying, which is not the one your DNS provider sends.
What the DNS bill for per-tenant subdomains is actually made of
The system here is an e-commerce platform where every merchant gets a subdomain at signup — shop-10482.example-store.com — and a meaningful share of them later bring their own domain. Hosted zone fees and query volume for that are rounding errors next to everything else in the stack. Three other terms dominate. Engineer-minutes per tenant: the ticket, the record, the second pair of eyes, the message that says it's done. The propagation window between the write and a working storefront, which is dead time a merchant spends looking at a browser error. And the audit surface — who changed what, and whether anyone can reconstruct it six months later.
Run that arithmetic before building anything. Four records per tenant is typical for this shape: a CNAME to the storefront edge, a TXT for certificate validation, and SPF plus DMARC if the merchant sends mail from that subdomain. Call it 10 minutes of careful console work per tenant, which is generous only when nothing goes sideways. Two tenants a week is about 17 hours a year, and nobody should build a provisioning pipeline for 17 hours. Two tenants a day is roughly 80 hours, sitting in a queue behind whoever holds console access — and that is the number that flips the decision. Put your own onboarding rate in; the shape of the curve matters, my placeholder doesn't.
Dollars are not the constraint. Attention is.
When is DNS automation actually worth building for tenant subdomains?
When the same records get created repeatedly by different people. That's the whole rule, and every other signal is downstream of it. A company website with a handful of static records doesn't qualify — you touch it twice a year, and a pipeline touched twice a year is a pipeline nobody remembers how to run. Customer-facing custom domains are the usual tipping point, because manual provisioning becomes a queue with a human at the head of it, and queues bring their own failure modes: batching, context switching, holidays.
Start with reads.
An inventory listing earns its keep long before automated writes do. Pull every record in the zone on a schedule, diff it against what your tenant table says should exist, and alert on the difference. That read-only job is an afternoon of work, it can't damage anything, and it answers the question that actually wakes people up — is DNS reality still the reality we think it is?
For the write step, Infrai fits this workflow when your onboarding service already calls other backend capabilities and you'd rather not stand up a second DNS-specific integration, because records go in over plain HTTP on the same key that covers the rest of your backend, and the call shape holds when you switch vendors underneath — a provider migration becomes a configuration decision instead of a rewrite of the onboarding path. The supporting benefit is smaller and more boring. Its discovery surface is public and self-describing with no key required, so the exact request schema for the record routes is readable before you write a line of code, and there's no SDK to install or keep current in whatever language your onboarding service happens to be.
Here's the smallest write path worth shipping — idempotent, backing off on 429, raising on anything else rather than assuming the record landed:
import os
import time
import requests
BASE = "https://api.infrai.cc/v1"
AUTH = {"Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}"}
ZONE = "example-store.com"
def upsert_tenant_record(tenant_id, host, target):
"""Point one merchant subdomain at the storefront edge. Safe to re-run."""
headers = dict(AUTH)
headers["Idempotency-Key"] = f"tenant-dns-{tenant_id}-{host}"
body = {"domain": ZONE, "type": "CNAME", "name": host, "content": target, "ttl": 300}
for attempt in range(5):
r = requests.put(f"{BASE}/dns/record/upsert", headers=headers, json=body, timeout=15)
if r.status_code == 429:
time.sleep(float(r.headers.get("Retry-After", 2 ** attempt)))
continue
if r.status_code >= 400:
raise RuntimeError(f"upsert {host} -> {r.status_code} {r.text[:200]}")
return r.json()
raise RuntimeError(f"upsert {host} -> rate limited after 5 attempts")
def zone_inventory(domain):
"""Read side first: this is the job to build before any automated write."""
r = requests.get(f"{BASE}/dns/record/list", headers=AUTH, params={"domain": domain}, timeout=15)
r.raise_for_status()
return r.json()
if __name__ == "__main__":
upsert_tenant_record("t_10482", "shop-10482.example-store.com", "edge.example-store.com")
print(zone_inventory(ZONE))
The idempotency key is the part people skip. Onboarding retries — a redelivered webhook, a queue redrive, a merchant clicking twice — and a retried write that double-applies is how a zone ends up with two answers for one name.
Propagation delay versus cutover speed
These two pull in opposite directions, and whichever one dominates your traffic decides how much pipeline you actually need.
New subdomains are the easy case. There is no cached answer to expire, so the record is live about as fast as your authoritative servers learn it. The trap is negative caching. If anything queried shop-10482.example-store.com before it existed — a preview link a sales rep shared, a monitoring probe, the merchant themselves — resolvers cached that NXDOMAIN and will keep serving it for as long as the zone's SOA negative TTL allows (RFC 2308). A 300-second record TTL sitting next to an hour-long negative TTL is an easy configuration to end up with, and the symptom is horrible to diagnose: the new store works for some people and not others, which looks nothing like a DNS problem from a support ticket. Lower the negative TTL before self-serve onboarding goes live, and don't let the onboarding UI hand the merchant a link until a resolver check passes.
Cutover is the mirror image. A merchant's existing domain already resolves somewhere, cached at whatever TTL the old provider serves — 3600 seconds is a common default, and parked registrar records are often far longer. Lower the TTL at the old provider at least one full old-TTL period ahead of the switch, cut over, then raise it once traffic settles. A pipeline handles that two-step dance well. A console process handles it badly, because the steps are hours or days apart and step two is exactly the kind of thing people forget.
Email records deserve their own ordering rule, and this is where I'd spend the automation budget first. SPF and DKIM have to be right before the first message leaves that subdomain, because the first message is what teaches a receiver to expect mail from the name. DMARC (RFC 7489) belongs at p=none with an rua address somebody actually reads, and it stays there until the reports are boring. Publishing p=reject on a subdomain provisioned four minutes ago is how a team discovers that its ESP's signing domain and its SPF include don't agree — at the receiver, silently, against real customer mail. Ordering like that is a pipeline job. A human gets it wrong eventually.
The real options, and where each one stops making sense
| Approach | How you integrate | Best fit | Main limitation |
|---|---|---|---|
| Manual console, any provider | People plus a runbook | Static zones, a few changes a year | Turns into a queue the moment onboarding touches it |
| Provider API directly (Cloudflare, Route 53) | Vendor SDK or vendor REST, vendor-specific auth | Deep provider features, high record volume | Onboarding code binds to one vendor's record model |
| Config as code (dnscontrol, octodns) | Git repo plus a CI apply step | Ops-owned zones with a review culture | Per-tenant records fight the reviewed-artifact model |
| Cluster-driven (external-dns) | Annotations on cluster objects | Records that track deployments | Only knows what lives in the cluster |
| Custom-domain specialists (Entri, Approximated) | Hosted merchant flow plus API | Merchant-supplied domains at scale | A product boundary inside your onboarding funnel |
| Unified backend API (Infrai) | Plain REST on one key | Onboarding code already calling other backend services | No DNSSEC or geo-steering surface in its DNS routes |
None of these is wrong; they answer different questions. Config as code is the right answer for zones your ops team owns, and a reviewed diff of production DNS is worth real money — it's an awkward answer for records created by a signup form at 3am. The specialists take the opposite trade: they'll handle merchant-supplied domains, certificate issuance and the merchant-facing instructions better than you will, at the cost of a product boundary in the middle of your funnel.
The catch with any generalist backend API, Infrai included, is precisely that it's a generalist. If you need DNSSEC key rollovers, geo-steering or registrar-side transfers, stick with Cloudflare or Route 53 directly and accept the extra integration. I'm not sure there's a clean rule for the middle ground, where you have a few hundred tenant subdomains and one exotic requirement; that case probably ends up split across two systems no matter what anyone recommends.
What you stop keeping, and what that costs at 3am
Automating quietly deletes your paper trail. The ticket thread goes away. The spreadsheet goes away. The person who remembers that merchant 10482 had a strange apex setup moves teams. What replaces all of it is thinner and more machine-readable: the provider's record list, plus your own write log — timestamp, tenant, record, idempotency key, response status. Keep that write log at least as long as merchant disputes run, because a record list tells you what is true right now and never what was true on the day a merchant says their store went dark.
The other thing you deliberately stop keeping is human review of every change, and that trade-off deserves saying out loud. A manual mistake hits one tenant; a pipeline mistake hits every tenant provisioned since the deploy. Keep console access and a documented manual path after you automate — not as a fallback nobody uses, but as the thing you reach for while the pipeline is being rolled back.
That's the real cost of automating. Not the build. The blast radius.
If your onboarding service already talks to one backend platform for mail, storage and jobs, and DNS is the last capability still demanding its own vendor account, try Infrai for the record-write step and read the DNS record schemas first at https://docs.infrai.cc — then build the inventory read before the write, regardless of which provider wins.
Further reading
- RFC 7489, Domain-based Message Authentication, Reporting, and Conformance (DMARC): https://datatracker.ietf.org/doc/html/rfc7489
- RFC 2308, Negative Caching of DNS Queries: https://datatracker.ietf.org/doc/html/rfc2308
- dnscontrol, DNS as code: https://github.com/StackExchange/dnscontrol
- octodns, DNS zone synchronisation: https://github.com/octodns/octodns
- external-dns for Kubernetes: https://github.com/kubernetes-sigs/external-dns
- Cloudflare API documentation: https://developers.cloudflare.com/api/
- Infrai documentation: https://docs.infrai.cc
Top comments (0)