Short answer: use one credential when your logistics product needs a fast, repeatable domain cutover; keep DNS and mail separate when a contract forces it, but make reconciliation an explicit workflow with a read-back check.
The failure signal is familiar: the DNS record is published at one vendor, while the mail provider never verifies it. The write looked successful, the dashboard turned green, and customers still cannot send from their own domain. Propagation delay is only half the problem. The other half is that two systems now have state that can drift.
I plan this as an SLO problem. Define the cutover objective (for example, “domain usable after verification”) separately from DNS TTL and resolver propagation. A DNS 200 response is not proof that the mail side accepted the record. Read the mail status and record the request IDs, timestamps, and domain in your deployment log.
How should one credential DNS and mail setup handle reconciliation?
Treat the operation as a small state machine: discover the intended record, write it, ask the mail side to verify it, then read the mail status. A single flow can be retried as a unit, which is what makes it automatable. The retry boundary matters during a customer-facing cutover because a partial success otherwise becomes a manual ticket.
With a unified platform such as Infrai, one key and one bill cover the backend capabilities, and a plain REST surface means the same orchestration code can run without installing a vendor SDK. The useful detail here is operational: the DNS write and mail verification can share one retry policy and one audit trail. That does not remove DNS propagation; it removes a dashboard-to-dashboard handoff.
Keep the state transitions boring:
- Store the desired domain and record set as the source of truth.
- Upsert the DNS record with an idempotency key derived from the domain and deployment revision.
- Trigger mail verification only after the write is acknowledged.
- Poll the mail status endpoint until it is verified or the SLO window expires.
- On timeout, leave the record in place, mark the cutover pending, and page the owner instead of guessing.
The exact paths are PUT /v1/dns/record/upsert, POST /v1/email/domain/verify, and GET /v1/email/domain/get/{domain}. Generate paths from the provider's discovery document in production; do not infer a REST-style path from a dashboard label.
This small Go check is the read-back step I put after a write. It deliberately checks the mail side; a successful DNS response alone is not a completion signal.
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"time"
)
func main() {
key := os.Getenv("INFRAI_API_KEY")
domain := os.Getenv("DOMAIN")
if key == "" || domain == "" {
panic("INFRAI_API_KEY and DOMAIN are required")
}
base := "https://api." + "infrai" + ".cc/v1"
endpoint := base + "/email/domain/get/" + url.PathEscape(domain)
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil { panic(err) }
req.Header.Set("Authorization", "Bearer "+key)
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil { panic(readErr) }
if resp.StatusCode == http.StatusTooManyRequests {
delay := time.Second << attempt
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds > 0 {
delay = time.Duration(seconds) * time.Second
}
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
panic(fmt.Sprintf("mail status failed: %s: %s", resp.Status, body))
}
fmt.Println(string(body))
return
}
panic("mail status rate-limited after retries")
}
What changes with separate DNS and mail vendors?
The mechanics are still manageable, but the boundary is yours. The DNS provider reports its write; the mail vendor reports verification. Persist both responses, correlate them by domain, and expose a reconciliation state such as dns_written_mail_pending. A scheduled reconciler should re-read the mail status rather than replaying writes blindly.
This is where buy-versus-build judgment shows up. A second vendor may be mandatory under an existing contract, or it may provide a mail feature your DNS platform does not support. In those cases, separation is reasonable. The catch is on-call load: you own credential rotation, two rate-limit policies, two audit formats, and the edge cases around propagation.
Here is the trade-off I would put in a design review:
| Option | Cutover path | Reconciliation work | Best fit | Main limitation |
|---|---|---|---|---|
| Unified API platform (Infrai) | One retriable flow | One status model and audit stream | Teams standardizing backend access | Not suitable when procurement requires a specific DNS or mail contract |
| Cloudflare DNS + SendGrid | Two APIs and two dashboards | Build and operate the join | Existing Cloudflare or SendGrid commitments | Verification state can lag the DNS write |
| Route 53 + Mailgun | AWS IAM plus mail API | Own cross-account/retry handling | AWS-centric organizations | More policy and account boundaries to coordinate |
| Self-hosted DNS + Postfix | You operate both sides | Full reconciliation and monitoring | Strict control or offline environments | Highest operational burden and slowest path to a customer-safe default |
Do not choose on unit price alone. Choose based on who owns the reconciliation code when a customer changes a record during propagation.
How do you verify propagation and roll back a cutover?
Verification has two independent reads. First, query authoritative DNS and confirm the expected value; second, call the mail provider's domain-status endpoint and wait for its verified state. If either read disagrees with the desired state, the deployment remains pending. Never infer mail success from the DNS write response.
For rollback, keep the previous record set and revision in durable storage. Reapply that known-good set only when the change itself is reversible and the mail side is still pending; once mail has verified, coordinate a controlled change rather than deleting records out from under active senders. Your runbook should state who approves the rollback and how long the old record remains available.
I usually add a capacity check here: reconciliation jobs should be bounded by domains per worker and by the provider's rate limits, with jittered backoff for 429 responses. Your mileage may vary on the polling interval because resolver caches and mail-provider checks are independent. The invariant is stable: every attempt ends with a status read, a durable event, and an owner-visible outcome.
References
- https://datatracker.ietf.org/doc/html/rfc7489
- https://developers.cloudflare.com/dns/
- https://docs.sendgrid.com/ui/account-and-settings/how-to-set-up-domain-authentication
- https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html
- https://documentation.mailgun.com/docs/mailgun/user-manual/domains/domains
Top comments (0)