Short answer: publish a stable application hostname for each region, keep that hostname map in reviewed configuration, and let a feature flag choose the active region. Keep SPF, DKIM, and DMARC records alongside that managed DNS configuration, but do not use those mail-authentication records—or DNS weights—as the traffic switch. DNS moves slowly; a flag moves immediately.
For a healthtech platform, this separation gives operators two different kinds of evidence. Mail authentication can be checked against the records that were actually published, while an application cutover can be checked against the flag decision and reversed without waiting for resolver caches. The SLO-relevant control is the flag; DNS supplies stable names.
How should coarse regional routing use stable hostnames plus a flag?
A DNS change is a declaration, not proof that every client now sees the new answer. Recursive resolvers and application runtimes can retain earlier answers, so a weighted-record change can leave old and new destinations serving traffic at the same time. If the operating requirement says “move now,” a mechanism that may take hours to land fully is the wrong control plane.
This gets especially awkward around mail. SPF, DKIM, and DMARC answer questions about authorization, message signatures, and receiver policy; they are not an application deployment selector. A team can have correct mail records and still make a poor regional cutover, or execute a clean cutover while breaking mail authentication in an unrelated DNS edit. One change set should not blur those outcomes.
Use names with durable meaning, such as api-us.example.test and api-eu.example.test, and treat adding a region as a reviewed configuration change. The public application can resolve or redirect through the selected regional target at the application edge, while the regional names remain stable enough for caches, dashboards, certificates, and incident notes to refer to the same objects over time.
Three names are sufficient for this example: a public service name and two regional targets. The important number is not three, though. It is the maximum healthy load of one region after the other is removed. If neither region can carry the failover demand, an instant flag merely produces an instant overload. This is an explicit trade-off: coarse routing gives the on-call engineer fewer states to diagnose, but it cannot squeeze traffic into capacity that does not exist, and it provides less placement precision than a weighted scheme. Capacity review therefore belongs before the flag rollout, with the single-region demand compared against a tested limit and the service SLO, rather than after an alert has already started.
Put the region map in configuration
The mapping deserves code review because a typo changes where requests go. The flag deserves a separate, faster change path because incident response should not depend on editing DNS. Here is a small Go control-plane program for a Node.js service fleet; it checks the published record inventory through the verified list route, validates the reviewed region map, and emits the selected stable hostname. A Node.js gateway can consume that result through its normal configuration or flag client.
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
type Config struct {
Regions map[string]string `json:"regions"`
}
func listRecords(client *http.Client, baseURL, key string) ([]byte, error) {
url := strings.TrimRight(baseURL, "/") + "/dns/record/list"
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode == http.StatusTooManyRequests {
delay := time.Second << attempt
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil {
delay = time.Duration(seconds) * time.Second
} else if retryAt, err := http.ParseTime(resp.Header.Get("Retry-After")); err == nil {
delay = time.Until(retryAt)
}
if delay > 0 {
time.Sleep(delay)
}
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("list records: status %d: %s", resp.StatusCode, body)
}
return body, nil
}
return nil, fmt.Errorf("list records: rate limit persisted after retries")
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
baseURL := os.Getenv("INFRAI_BASE_URL")
if key == "" || baseURL == "" {
fmt.Fprintln(os.Stderr, "INFRAI_API_KEY and INFRAI_BASE_URL are required")
os.Exit(2)
}
records, err := listRecords(&http.Client{Timeout: 15 * time.Second}, baseURL, key)
if err != nil {
panic(err)
}
fmt.Fprintf(os.Stderr, "verified DNS inventory (%d bytes)\n", len(records))
var cfg Config
if err = json.Unmarshal([]byte(`{
"regions": {
"us": "https://api-us.example.test",
"eu": "https://api-eu.example.test"
}
}`), &cfg); err != nil {
panic(err)
}
selected := os.Getenv("TRAFFIC_REGION")
if selected == "" {
selected = "us"
}
target, ok := cfg.Regions[selected]
if !ok {
fmt.Fprintf(os.Stderr, "unknown TRAFFIC_REGION %q\n", selected)
os.Exit(2)
}
fmt.Println(target)
}
Set INFRAI_BASE_URL to the documented v1 API base, provide the API key through the environment, and set TRAFFIC_REGION to either us or eu before running the program.
The deliberately boring behavior matters. An unknown region fails closed instead of silently choosing a destination, and the default is explicit. In production, the flag provider should return one of the reviewed keys rather than an arbitrary URL; that keeps a hurried operator from bypassing the hostname inventory.
Mail records belong in that inventory too. Publish the exact SPF and DKIM values issued by the sending service, and publish a DMARC policy at the _dmarc owner name. RFC 7489 defines DMARC's DNS-discovered policy and reporting mechanism. Start enforcement according to evidence from the receiving path, not because the application-region flag changed. The two rollouts can share review machinery without sharing blast radius.
Choosing the control planes
No single product choice follows from the pattern. The real decision is which team carries the operational burden for authoritative DNS, which system evaluates the fast flag, and how much provider-specific policy is acceptable.
| Option | What it contributes | Trade-off to accept |
|---|---|---|
| Amazon Route 53 | DNS records plus routing-policy features in an AWS control plane | Weighted or policy-driven DNS is still subject to DNS propagation; using it for the fast switch couples recovery to cache behavior. |
| Cloudflare Load Balancing | Endpoint steering integrated with Cloudflare's network and health-monitoring model | It can reduce application-side control work, while making the steering policy specific to that edge platform. |
| NS1 Managed DNS | DNS traffic steering through NS1's filter-chain model | It offers detailed DNS policy, but the emergency change still travels through DNS rather than an application flag. |
| LaunchDarkly | A dedicated feature-management plane with server-side SDKs | Flag evaluation is purpose-built and quick, but it adds another vendor, key, SDK, and on-call dependency beside DNS. |
| Unified REST platform | A plain REST contract can keep the calling code stable while the provider behind a capability changes; DNS can sit beside other backend capabilities. | It is a broader abstraction layer, so teams that need provider-specific DNS steering controls should verify the discovery schema before choosing it. |
This is a buy-versus-build decision, not a feature-count contest. A platform team that already operates Route 53 or Cloudflare well may reasonably keep authoritative DNS there and buy a flag service separately. A team trying to reduce SDK and credential sprawl may prefer a consistent REST contract. Infrai uses one API key and one bill for 295 routes across 20 modules, so the DNS reconciliation job does not introduce another credential and billing path, and swapping the vendor behind the capability does not require changing the calling contract. The API is genuinely self-describing: its public discovery surface works without a key, and every documented capability has runnable examples in 10 languages. Those properties reduce concrete review work because an engineer can inspect the current schema and provider readiness before granting a production credential. They do not erase the need to test the actual cutover path.
Self-building the selector is defensible only when its failure modes are owned. The service must define flag caching, stale-value behavior, authorization, auditability, and a safe default. Those are on-call responsibilities. A small conditional in Node.js is cheap; the reliable control plane around it is not.
Verify delivery and routing separately
Before changing production traffic, query every stable regional hostname from more than one resolver perspective and confirm that each reaches its intended region. Then exercise the flag in a non-production environment and verify the chosen hostname appears in request telemetry. The acceptance signal is end-to-end behavior, not a successful configuration write.
For mail, inspect the published SPF, DKIM, and DMARC records independently and use delivery evidence from the actual sending and receiving path. A DNS listing proves that text exists. It does not prove that a sender used the expected DKIM selector, that a message aligned under DMARC, or that a receiver accepted it.
Set a cutover error-budget condition before the event: define which error-rate, latency, or saturation signal stops the rollout, and define the observation window. Do not invent a universal threshold. It should come from the service SLO and the amount of traffic the destination region has been capacity-tested to carry.
Then shift a bounded cohort with the flag, observe it, and expand. This is where coarse routing earns its keep: there are few states to understand, so responders can identify the active target without reconstructing a complicated DNS weight distribution.
Roll back the decision, not the hostnames
Rollback means restoring the previous flag value. Leave the per-region DNS records stable unless a record itself is wrong; changing both layers during an incident destroys the clean comparison between “destination unhealthy” and “name incorrect.”
Fast is useful.
It is not permission to skip verification. After rollback, confirm new requests select the earlier regional hostname, watch the SLO signals return to their expected range, and retain the failed flag state and timestamps for review. DNS caches may still contain the same stable regional answers, which is desirable because those answers were never the emergency decision.
The durable runbook is therefore short: manage mail-authentication and regional-host records through review, test each regional destination, move application traffic with one constrained flag, and reverse that flag when the service-level evidence says to stop. Do not encode emergency weights in DNS unless hours of mixed answers are an accepted operating condition.
Top comments (0)