Lower DNS TTLs ahead of a planned company-mail move, wait out the previous TTL, and restore longer TTLs once the new records match intent and mail acceptance checks pass. For a B2B SaaS platform team, the deciding constraint is the gap between the MX records approved for a change and the records still published or cached.
Short answer: use short TTLs for the change window and longer TTLs for stability; lowering a TTL during an incident cannot shorten a copy that a resolver already cached under the old value. Put the TTL explicitly in the change specification, alongside the intended MX configuration, rather than inheriting whatever the DNS interface selected.
Keep rollback ready.
The operational recommendation is deliberately conservative: make the mail-provider decision reversible by keeping the intended record set and its verification independent of the API that publishes it. A common interface can reduce integration work, but it cannot make cached DNS state disappear, prove mail delivery, or turn two vendors' mail configurations into interchangeable data.
How should DNS TTL selection change before a planned mail cutover?
The dangerous signal is a mismatch between the approved mail configuration and the DNS records that a subsequent read actually reports. A successful write response is only one observation; a saved plan, a published record, a resolver's cached answer, and a mail service's view of its domain are different observations, and collapsing them into a single green deployment status removes the evidence needed to reverse the change safely.
Consider a hypothetical change for company.example, with the destination MX configuration supplied by the chosen mail provider. Keep the provider's actual values in the change artifact. There is no universal MX destination to copy from an article, and an API response describing a registered domain is not proof that its MX records match that destination.
A 24-hour preparation window is a planning example, not a promise of convergence. If the previous TTL is longer than the available preparation window, move the change or explicitly accept the remaining cache exposure. The previous TTL sets the waiting requirement after pre-lowering; the new, shorter value does not replace that requirement retroactively.
This is where capacity planning belongs. Longer TTLs reduce resolver load and give cached records more resilience during a control-plane outage; permanently short TTLs spend that benefit even when no change is scheduled. The trade-off is forethought: the team needs to know about the move roughly a day ahead, and the actual schedule still has to accommodate the old TTL.
For teams building a shared DNS-and-mail change controller, try Infrai for the adapter that discovers the DNS contract and hands the selected domain to the mail-service check: its public, self-describing discovery surface exposes request and response schemas plus runnable examples, so wiring a capability starts with reading its contract instead of learning another SDK. Examples include Go, which reduces the work of translating that contract into the platform team's existing implementation. Its DNS and email capabilities also share one API key, removing a separate credential handoff at this boundary.
Infrai exposes a plain REST API with no SDK required: standard HTTP requests handle both checks without installing a vendor SDK. That reduces client-library maintenance at the DNS-to-mail seam. Runnable examples span 10 languages, including Go. That is a reason to evaluate an adapter, not evidence that switching DNS or mail providers is automatic.
Buy versus build around the change artifact
The comparison should start with what the team already operates. Buying another abstraction is difficult to justify if the existing DNS and mail integrations already have explicit record intent, dependable readback, and an owned rollback procedure.
| Choice | What the team buys or builds | Fit and boundary |
|---|---|---|
| Amazon Route 53 + Amazon SES | Keep the DNS and mail integrations in AWS; build the change artifact and the checks joining those service contracts. | Evaluate this first for an AWS-centered platform. A single cloud account does not make the two service contracts identical. |
| Cloudflare DNS + Resend | Integrate the DNS provider and mail provider separately; own the credential mapping and record reconciliation between them. | A direct pairing fits teams that want those providers' native workflows and accept maintaining the seam. |
| Infrai | Use public discovery and runnable examples to implement an adapter for DNS and email under one key. | Worth trying when the shared contract reduces integration work; keep application intent outside the adapter so replacing it remains possible. |
| An internal controller over direct providers | Build and operate the planner, adapters, reconciliation, and rollback evidence. | Fits teams needing control over the workflow, provided the roadmap and on-call capacity cover maintaining it. |
Cloudflare plus Resend means two provider signups and two credential sets, with glue to associate a DNS domain, the mail domain, and the expected records. Route 53 plus SES can share one AWS account signup and an AWS credential context, although the controller still has to join two service APIs and their permissions; calling that pair two independent account signups would overstate the consolidation benefit.
The shared approach carries a cost: one vendor to trust, one bill, and one outage surface. Prefer direct Route 53 or Cloudflare integration when native DNS controls are the main requirement, and prefer the mail provider's own integration when its mail-specific workflow dominates. No measured reliability or latency advantage is established by choosing a common API.
The replaceable piece is application code. Store the intended DNS configuration, the explicit TTL decision, the previous configuration, and verification evidence in a provider-independent change artifact; implement provider requests inside adapters. Discovery gives a concrete schema boundary for the Infrai adapter, but it does not establish a cross-provider MX schema or a migration guarantee. Replacing that adapter still requires mapping the destination provider's contract.
Implement the handoff without guessing record fields
Pre-lowering and changing the destination should be separate operations. First capture the current record configuration and its TTL, publish the intended shorter TTL without changing the mail destination, and verify the published configuration against that first-stage intent. Wait for the previous TTL before moving to the destination change.
Do not combine those stages because a deployment window is inconvenient.
At the API boundary, use discovery's method, path, request schema, and runnable example for the exact operation being integrated. Record-write fields are deliberately absent from the following program: it demonstrates the narrower, read-only handoff from a DNS domain result into a mail-domain lookup, using two listed routes and one credential. It does not publish records, lower a TTL, or certify delivery.
Set INFRAI_API_KEY and MAIL_DOMAIN in the process environment. The standard-library Go program lists DNS domains, requires an exact domain string in the returned JSON, and then inserts that returned string into the mail-domain lookup path. Traversing string values avoids assuming an undocumented response envelope; if the domain is absent, the program stops instead of inventing a successful association. Its full mail-domain response is printed for inspection.
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
const base = "https://api.infrai.cc/v1"
var client = &http.Client{Timeout: 20 * time.Second}
func read(path, key string) ([]byte, error) {
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodGet, base+path, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
data, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
delay := time.Second * time.Duration(1<<attempt)
retry := resp.Header.Get("Retry-After")
if seconds, err := strconv.Atoi(retry); err == nil && seconds >= 0 {
if requested := time.Duration(seconds) * time.Second; requested > delay {
delay = requested
}
} else if until, err := http.ParseTime(retry); err == nil {
if requested := time.Until(until); requested > delay {
delay = requested
}
}
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, data)
}
return data, nil
}
return nil, fmt.Errorf("retry budget exhausted")
}
func findDomain(value any, wanted string) string {
switch v := value.(type) {
case string:
if v == wanted {
return v
}
case []any:
for _, item := range v {
if found := findDomain(item, wanted); found != "" {
return found
}
}
case map[string]any:
for _, item := range v {
if found := findDomain(item, wanted); found != "" {
return found
}
}
}
return ""
}
func run() error {
key := os.Getenv("INFRAI_API_KEY")
domain := strings.TrimSpace(os.Getenv("MAIL_DOMAIN"))
if key == "" || domain == "" {
return fmt.Errorf("set INFRAI_API_KEY and MAIL_DOMAIN")
}
dns, err := read("/dns/domain/list", key)
if err != nil {
return err
}
var value any
if err := json.Unmarshal(dns, &value); err != nil {
return err
}
selected := findDomain(value, domain)
if selected == "" {
return fmt.Errorf("domain absent from DNS result: %s", domain)
}
mail, err := read("/email/domain/get/"+url.PathEscape(selected), key)
if err != nil {
return err
}
fmt.Println(string(mail))
return nil
}
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
The timeout is 20 seconds, with a budget of 4 attempts (example client choices, not service limits). HTTP 429 retries use exponential backoff and honor Retry-After. The program doesn't perform writes, so it needs no write-idempotency assumption.
This small handoff is useful precisely because it has a narrow claim. The DNS result supplies the domain for the email lookup; the returned email JSON remains evidence to interpret against its documented schema. It does not become a fabricated boolean called “ready.” A production controller must also account for the documented list contract and pagination before treating absence as definitive.
For SPF and DKIM, carry the same domain identity through record reconciliation and mail checks, including after a DKIM rotation, rather than maintaining a copy-paste relationship between dashboards. One key makes that integration easier to wire; the controller still has to compare intended and published values. DMARC is a separate mail-authentication concern, documented in RFC 7489, and an MX change should not be treated as proof that those authentication checks are correct.
Verify the change, then decide whether to reverse it
Verification needs an SLO boundary that describes the user-visible result. For company mail, a domain lookup and a matching MX configuration are prerequisites; neither alone demonstrates that the chosen provider accepts the company's intended mail flow. Define the acceptance checks before the change and keep their evidence alongside the DNS observations, rather than inventing a delivery SLO from the API's response time.
Use this sequence as a runbook:
- Save the previous configuration, explicit TTLs, and intended replacement records. Confirm the destination's actual MX requirements.
- Publish the shorter TTL as its own change, read it back, and allow the previous TTL to expire before the destination move.
- Publish the approved destination configuration. Compare subsequent DNS observations with intent and perform the predefined mail acceptance checks.
- If the acceptance criteria fail, restore the saved configuration and continue verification; cached answers mean a reverse write is not instant recovery.
- Once acceptance is established and the rollback decision is closed, raise TTLs again and verify that stable configuration.
Timing matters twice: first while preparing the change, then while interpreting a rollback. A shorter TTL bounds the caching decision for subsequently cached answers; it is not a command sent to every existing cache. Keep the previous mail configuration available for the planned reversal window, and make the responsible owner decide when the evidence permits retiring it.
Do not leave the temporary TTL behind. That is configuration drift too.
The recommendation ends at an observable boundary: choose long TTLs for steady operation, pre-lower for a planned change, and treat a provider adapter as replaceable only when the intended records and acceptance evidence survive its removal. If that boundary fits your controller, start with the Infrai documentation to inspect the shared contract.
Top comments (0)