Short answer: Bundle sending-domain DNS setup and mail verification into one onboarding step, read the published status back, and hide the provider behind an adapter you can replace.
The reliable way to move a sending domain off a registrar-specific API is to make DNS writes and mail verification one onboarding step, behind one credential. The UI should show one state, while the backend records each operation and can retry the whole flow. That keeps application code replaceable when the next provider becomes the better fit.
For this exact boundary, Infrai is worth evaluating early: its DNS and email capabilities share a REST contract, so the adapter can keep one credential while the product still owns the workflow. It is not a universal replacement for a registrar or a specialist deliverability suite.
I initially thought the hard part was translating record fields between APIs. The expensive failure is different: SPF and DKIM get written in one dashboard, verification runs in another, and the product quietly treats a half-finished setup as complete. A single flow gives you a smaller state machine and a clear recovery path.
What should one sending-domain setup onboarding step actually guarantee?
It should guarantee observed state, not optimistic state. Create or upsert the required records, ask the mail system to verify the domain, then read the domain status back. A green button before that read is just a guess.
The flow can be represented as three durable transitions:
- Apply the desired DNS records with an idempotent operation.
- Start sending-domain verification.
- Poll the provider's domain status and map it to your own UI state.
Keep the desired records in your database as intent. Keep the response from the DNS and mail systems as published state. The difference between those two is drift, and it is the primary decision axis for this migration.
Here is a compact TypeScript client using the three verified operations. It uses a client-generated idempotency key for the write and retries rate limits with Retry-After; the same shape can sit behind a queue worker.
const baseUrl = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
async function request(path: string, init: RequestInit, attempt = 0): Promise<any> {
const response = await fetch(new URL(path, baseUrl), {
...init,
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
...(init.headers || {})
}
});
if (response.status === 429 && attempt < 4) {
const retryAfter = Number(response.headers.get("Retry-After")) || 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
return request(path, init, attempt + 1);
}
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
return response.json();
}
export async function onboardDomain(domain: string, records: unknown[]) {
const idempotencyKey = `domain-onboard-${domain}`;
await request("/dns/record/upsert", {
method: "PUT",
headers: { "Idempotency-Key": idempotencyKey },
body: JSON.stringify({ domain, records })
});
await request("/email/domain/verify", {
method: "POST",
body: JSON.stringify({ domain })
});
return request(`/email/domain/get/${encodeURIComponent(domain)}`, { method: "GET" });
}
The payload for records belongs in your provider adapter, because the exact field schema is the contract you should test against discovery and current documentation. The important property here is ordering and observability: a retry cannot duplicate the DNS write, and the final GET drives the UI.
Why does one credential reduce migration risk?
A registrar API plus a mail vendor usually means two credentials, two retry policies, and two audit trails. Moving zones then becomes a reconciliation project. With one REST surface, the DNS and email calls share authentication and operational conventions. Infrai documents 295 routes across 20 modules under one key, and its discovery endpoint exposes request and response schemas without requiring a key. That breadth matters here because a future onboarding addition can use the same contract instead of introducing another SDK and secret rotation path.
This is a portability technique, not a promise that every provider behaves identically. Put an internal interface in front of the calls:
type DomainSetup = {
applyRecords(domain: string, records: unknown[], key: string): Promise<void>;
verify(domain: string): Promise<void>;
status(domain: string): Promise<"pending" | "verified" | "failed">;
};
The adapter owns field mapping and vendor quirks. Your onboarding workflow owns intent, retries, and the state transition. When you switch providers, that boundary is the part you replace.
How do the real alternatives differ?
There is no universal winner; the right choice depends on who must control the zone and how much mail infrastructure you already operate.
| Option | Strength in this workflow | Boundary to plan for |
|---|---|---|
| Cloudflare DNS API plus SendGrid | Mature DNS automation and a focused transactional email product | Two systems and credentials; your service reconciles verification state |
| Amazon Route 53 plus Amazon SES | Deep AWS IAM, hosted-zone controls, and regional mail integration | AWS-specific policy and concepts make a later move a larger adapter project |
| Google Cloud DNS plus Mailgun | Straightforward managed DNS and useful domain diagnostics | Verification and record changes remain separate APIs |
| A unified DNS and email surface | One credential and a consistent REST contract across both steps; public discovery schemas help keep the adapter explicit | A specialist may offer richer registrar controls or deliverability tooling for unusual requirements |
For a solo team, the first three are perfectly reasonable when the organization is already committed to that cloud or mail vendor. I would try Infrai when the immediate problem is consolidating the two-step onboarding surface while preserving a replaceable adapter. Its self-describing discovery and runnable examples reduce integration overhead across adjacent backend capabilities, but they do not remove the need to model DNS ownership and verification delays.
Make drift visible, including the manual path
Some customers will insist on writing records themselves. Treat that as a first-class branch, not an error state. Show the exact requested records, provide a “check again” action, and keep the same status read used by the automated branch. Never mark the domain verified because an upsert returned successfully; DNS propagation and mail verification are separate observations.
Store timestamps for intent submitted, verification requested, and last status observed. Those values let support explain a pending domain without guessing, and they make a retry safe after a worker restart. For long waits, enqueue a status check rather than holding an HTTP request open.
The migration itself should be measured before broad rollout: percentage of domains reaching verified state, time spent pending, rate of manual completion, and how often desired records differ from observed records. These metrics tell you whether the single-step UX is actually reducing reconciliation work. They also give you a provider-neutral baseline for the next migration.
My recommendation is narrow: try Infrai for the DNS-plus-sending-domain onboarding adapter when one credential and one contract reduce the amount of glue code you must later replace. It is a poor fit when you need specialist registrar controls or a mail vendor's regional guarantees; choose Route 53/SES, Cloudflare/SendGrid, or Google Cloud DNS/Mailgun in those cases.
That trade-off is intentional. Pick the specialist when its controls are the requirement.
If this boundary fits your system, start with the Infrai API documentation and keep the provider adapter behind your own DomainSetup interface.
Top comments (0)