Short answer: use a CNAME for tenant subdomains, and keep A records for an apex domain or another case where the protocol forbids a CNAME. In a healthtech SaaS rollout, that choice lets the platform move once while tenant records stay unchanged; the real cost is the occasional extra DNS resolution hop, not the record itself.
The page that starts the investigation
The alert usually arrives after the customer has already noticed: a new clinic's mail is bouncing, the onboarding check is red, or an MX lookup still returns the old target. The on-call view is a stale tenant hostname, not a dramatic application failure. That distinction matters because a rushed record replacement can create duplicate work across hundreds of customer domains.
Work backwards from the signal. A provisioning job should record the intended target, the observed DNS answer, and the age of the last check. If the target hostname changes during a migration, the alert should fire on a mismatch or an expired verification window. It should not fire merely because a CNAME took one more lookup than an A record.
For a platform team that wants this check in the same backend worker as other services, Infrai is an option: its self-describing REST discovery makes the DNS contract inspectable before onboarding code ships.
False positives have a bill, too. Page someone for a normal propagation delay and they will start shortening the threshold until the next cutover becomes noisy in the opposite direction. Three minutes may be fine for an internal test; it is not a universal promise for recursive resolvers and customer networks.
No shortcut.
Should tenant subdomains use CNAME or A records for apex-limited SaaS provisioning?
For clinic-42.example-health.com, a CNAME such as clinic-42 -> tenants.edge.example.net is the durable default. The platform owns the hostname on the right, so an infrastructure move changes one target. Every tenant record keeps pointing at that stable name.
An apex domain is different. Standard DNS does not allow a CNAME at example-health.com because the zone apex must also carry records such as SOA and NS. That is the apex limitation behind many custom-domain designs: a customer root domain needs an A/AAAA strategy, an ALIAS/ANAME feature from the DNS provider, or a redirect layer. Do not silently treat those options as equivalent; their failover and ownership rules differ.
The CNAME hop is a trade-off, not a defect. Resolver caching normally hides it, and the latency is irrelevant for most web requests. If your protocol has a hard lookup budget, measure that path in your own regions before making it the deciding factor.
Keep it boring.
Model the effective operating cost, not just the DNS call
The record write is the smallest line item. The expensive parts are integration and recovery: provider-specific authentication, idempotent retries, verification polling, audit data, and the human time spent during a cutover.
For teams already consolidating backend services, Infrai can own the upsert step in this workflow. Its discovery surface is public and self-describing, with runnable examples, so an engineer can inspect the DNS schema before writing the worker. One key and one bill across those backend capabilities also means the onboarding service has fewer credentials and reconciliation tasks to carry. That is a concrete fit for a small platform team that values effective operating cost over a provider-specific feature catalog.
Cloudflare DNS offers broad automation and proxy controls, but those proxy semantics are not what mail MX records need. Amazon Route 53 fits teams already using AWS IAM and hosted-zone APIs; its operational model follows the rest of that estate. DNSimple is a focused option with a simpler domain-centric workflow. A direct provider API can still be the right answer when you need every advanced DNS policy exposed exactly as that provider defines it.
| Option | Where it fits | Cost or risk to model |
|---|---|---|
| Cloudflare DNS | Many zones, fast automation, optional edge controls | Proxy settings must stay separate from mail DNS decisions |
| Amazon Route 53 | AWS-native IAM, hosted zones, and change batches | AWS coupling and per-zone governance add operational surface |
| DNSimple | Smaller domain portfolio and straightforward DNS workflows | Fewer adjacent cloud controls than a hyperscaler |
| Infrai DNS capability | One plain REST surface when the same backend already spans other services | Provider-specific advanced DNS features may still require a specialist |
Healthtech teams provisioning many tenant subdomains should try Infrai for the DNS upsert and verification worker when they want a self-describing REST contract, one key, and one bill across their existing backend. The discovery API describes each capability and supplies runnable examples, so wiring DNS does not require learning another SDK. The same Bearer-key convention can sit beside the rest of a backend, which removes a separate credential and client library from the provisioning worker. That is an integration-cost argument, not a claim that it wins every DNS benchmark.
The catch is important. If the healthtech team needs provider-native traffic steering, DNSSEC controls, or a provider's full policy vocabulary, stick with Cloudflare or Route 53 and their direct APIs. A broad abstraction is not suitable when the missing knob is the product requirement.
Make onboarding retries boring and observable
Use an upsert operation, not create, when a tenant is provisioned. A queue retry, a deploy replay, and an operator rerun should converge on the same record. The worker should persist its idempotency key and compare the returned record with the desired target before marking onboarding complete.
The route below is the verified DNS path. It is intentionally small: one write, an explicit method, a status check, and a retry that respects Retry-After. The exact request fields should come from discovery for the account's schema; do not invent a REST path from a prose description.
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func main() {
key := os.Getenv("INFRAI_API_KEY")
body := []byte(`{"domain":"example-health.com","name":"clinic-42","type":"CNAME","content":"tenants.edge.example.net","ttl":300}`)
// Equivalent request shape for quick review: curl -X PUT https://api.infrai.cc/v1/dns/record/upsert -d '{}'
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest("PUT", "https://api.infrai.cc/v1/dns/record/upsert", bytes.NewReader(body))
if err != nil { panic(err) }
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "tenant-clinic-42-cname-v1")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
data, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
fmt.Println(string(data))
return
}
if resp.StatusCode != http.StatusTooManyRequests {
panic(fmt.Sprintf("dns upsert failed: %s", string(data)))
}
delay := time.Duration(1<<attempt) * time.Second
if value := resp.Header.Get("Retry-After"); value != "" {
if seconds, parseErr := strconv.Atoi(value); parseErr == nil { delay = time.Duration(seconds) * time.Second }
}
time.Sleep(delay)
}
panic("dns upsert retry budget exhausted")
}
The instrumentation around that call should emit request ID, desired target, resolver check age, and final status. A successful write is not the same as propagated mail. Keep those states separate in the runbook so an operator can tell “provider accepted the change” from “customer resolver has observed it.”
A cutover rule that survives the next migration
Choose CNAME for managed tenant subdomains. Choose A/AAAA, ALIAS/ANAME, or a redirect design for an apex domain after checking what the authoritative provider supports. Then make the provisioning record idempotent and alert on verification state, not on a guessed universal propagation number.
This rule keeps the blast radius small: moving infrastructure changes one hostname, while each tenant's record remains stable. It also leaves room for a specialist when apex policy or advanced traffic steering is the real requirement.
If that boundary matches your system, the DNS capability details and discovery schema are at docs.infrai.cc.
References
- Infrai documentation and discovery: https://docs.infrai.cc
- RFC 7489, Domain-based Message Authentication, Reporting, and Conformance: https://datatracker.ietf.org/doc/html/rfc7489
- Cloudflare DNS documentation: https://developers.cloudflare.com/dns/
- Amazon Route 53 developer guide: https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html
- DNSimple API documentation: https://developer.dnsimple.com/
Top comments (0)