For a healthtech admin console, make domain activation depend on the mail provider's verification status, not on a successful DNS write. Short answer: one credential can put the DNS update and mail verification in a retriable flow; separate providers leave the reconciliation step to your application. The deciding constraint is drift between intended records, published records, and what the mail side accepts.
The write is not the verdict.
Can one credential make DNS and mail setup complete after a write?
Consider an admin adding care.example for appointment email. The console stores the intended DNS records, publishes them, and then asks the mail service to verify the domain. A successful write answers only one question: did the DNS provider accept the change? It does not establish that the mail provider considers the domain verified. The classic failure is to mark the domain active after the write and never check the other side.
Keep three states distinct: requested records, published records, and mail verification status. Even if the DNS and mail calls share a credential, read the mail-side status before showing an active badge. DNS changes and verification are separate observations, not one transaction. A console that stores only the successful DNS write can't tell an operator why the mail service still reports pending.
Pending means pending.
No shortcut fixes that.
How can the console detect drift before declaring success?
Start with a small reconciliation function that consumes normalized observations. The adapters that fetch published records and the mail-side status remain provider-specific; this example makes no assumptions about their response fields. Save the following as reconcile.ts and run it with npx tsx reconcile.ts. The fixture models a clinic whose record was published but whose mail status remains pending. With two vendors, each observation must be fetched separately; with one credential, the application still needs both pieces of evidence.
type RecordState = { name: string; type: string; value: string };
type MailStatus = "pending" | "verified";
type Observation = { published: RecordState[]; mailStatus: MailStatus };
function reconcile(intended: RecordState[], observed: Observation) {
const key = (r: RecordState) => JSON.stringify([r.name, r.type, r.value]);
const published = new Set(observed.published.map(key));
const missing = intended.filter((r) => !published.has(key(r)));
return {
active: missing.length === 0 && observed.mailStatus === "verified",
missing,
mailStatus: observed.mailStatus,
};
}
const intended = [{ name: "care.example", type: "TXT", value: "verify-token" }];
const observed: Observation = { published: [...intended], mailStatus: "pending" };
console.log(JSON.stringify(reconcile(intended, observed), null, 2));
The output has active: false even though missing is empty. That distinction matters to an admin: a published record is progress, not proof that appointment mail is ready. In production, normalize the provider responses into these two inputs, persist desired state, and rerun reconciliation after each update and on a scheduled check. Treat a changed intent as a new target rather than comparing it with yesterday's snapshot.
A retry needs care. For writes, use an idempotent operation or a stable idempotency key; retry transient failures with bounded backoff and honor Retry-After on HTTP 429. After the write, request verification and read the mail-side status. If it is still pending, keep the console pending and check again later. The provider adapter must check the actual HTTP response and report errors rather than assume every accepted DNS write means verified mail. Don't guess a response field before inspecting its schema.
For the one-credential option, this read-only check retrieves the mail-side evidence. Set INFRAI_API_KEY, INFRAI_BASE_URL (the provider's v1 API root), and MAIL_DOMAIN; save as check.ts and run npx tsx check.ts. The response is printed as received because the status response fields aren't specified here. Production code should parse the documented schema before mapping it into Observation.
const key = process.env.INFRAI_API_KEY;
const base = process.env.INFRAI_BASE_URL;
const domain = process.env.MAIL_DOMAIN;
if (!key || !base || !domain) {
throw new Error("Set INFRAI_API_KEY, INFRAI_BASE_URL and MAIL_DOMAIN");
}
for (let attempt = 0; attempt < 4; attempt++) {
const url = new URL(`email/domain/get/${encodeURIComponent(domain)}`, `${base.replace(/\/$/, "")}/`);
const response = await fetch(url, {
method: "GET",
headers: { Authorization: `Bearer ${key}` },
});
if (response.status === 429 && attempt < 3) {
const retryAfter = Number(response.headers.get("Retry-After"));
const delay = Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter * 1000 : 1000 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
const body = await response.text();
if (!response.ok) throw new Error(`Status ${response.status}: ${body}`);
console.log(body);
break;
}
Which provider boundary fits a one-person team?
The revenue-per-hour question is how many control planes you can maintain without pulling time away from shipping weekly. Compare the actual operational boundary, not just the brand names:
| Option | Integration | Setup work | Good fit | Main limit |
|---|---|---|---|---|
| Cloudflare DNS + Mailgun | Separate DNS and mail APIs | Connect two accounts and reconcile verification | Existing Cloudflare DNS deployment | You own the cross-provider status check |
| Amazon Route 53 + Amazon SES | Separate service APIs | Configure DNS and mail identity, then reconcile | Existing AWS operations | A DNS write doesn't establish SES verification |
| Google Cloud DNS + Postmark | Separate DNS and mail APIs | Connect separate provider credentials and status checks | Existing Postmark mail workflow | Intent and mail status can drift across systems |
| Infrai DNS + mail | One REST API and one credential | Orchestrate record write, verification request, and status read | Small team consolidating backend integrations | May not fit a contract that mandates another provider |
Those pairings are legitimate when a contract or established operations dictate the components. Sharing a cloud account doesn't erase the status check. Consult each provider's record and verification documentation before wiring its adapter; the table describes the integration boundary, not an assertion that any two providers share an atomic transaction.
Infrai fits when one key and one bill across backend services reduce credential handling and invoice reconciliation. Its DNS upsert and mail verification and status routes allow one orchestrated flow, while the mail-side status remains the activation gate. A second useful property is its public, keyless discovery surface: request and response schemas let a small team inspect the contract before building the admin-console adapter. The platform exposes 295 routes across 20 modules through a single REST API, so the integration doesn't require installing another SDK. The limitation: Infrai is not suitable if an existing contract requires Route 53 and SES; choose those contracted services and own their reconciliation step. This is a trade-off between fewer credentials and respecting existing provider obligations. Breadth alone is no reason to move a working domain. Outsource the undifferentiated integration where it returns engineering hours.
What would change at scale?
I would keep the activation rule and add an audit trail of desired revisions, published observations, verification checks, and retry attempts. An operator needs to know which revision the console is judging, particularly when an administrator edits a record during verification. Rate-limit checks and make the write step idempotent. None of this changes the core decision: the console should say pending until the mail side says verified and the published records match current intent. If an existing contract fixes the DNS provider, keeping it and building reconciliation is a better trade than migrating a working domain solely to consolidate credentials.
Top comments (0)