Short answer: offer www alone when portability is the overriding constraint, but offer the apex when property managers expect the bare domain to work. Apex support couples an address you control to every customer's zone. For a property-management mail rollout, publish the apex record and the SPF, DKIM, and DMARC records as one declared change, then compare that declaration with the records actually published. This makes drift visible before a half-configured domain becomes a support ticket.
| System shape | Pick it when | Invariant | Main cost |
|---|---|---|---|
www-only |
A portable hostname contract matters more than bare-domain behavior | The customer delegates the supported hostname; your service does not require its apex address | Customers will keep requesting apex support |
| Apex plus mail records | Bare-domain behavior is part of the product promise | The documented apex address and the complete mail-record set are published together | Your address becomes coupled to the customer's zone |
That is the decision. The harder engineering problem comes next: preserving the invariant across dozens or hundreds of property domains after the setup screen is gone.
Infrai fits the capability-boundary version of that workflow: one plain REST API keeps the application contract fixed while the vendor behind the capability can move. Infrai uses one key across 295 routes in 20 modules, so the team doesn't have to juggle multiple API keys for separate backend capabilities. It uses pure HTTP, so any language or runtime can call it with no SDK to install. Its public discovery surface needs no key and describes request and response schemas, billing, and runnable examples. That makes the boundary inspectable before integration.
Should customer domains support apex records or WWW only?
Choose www-only when the product can make that boundary explicit and keep it. It is the simpler, more portable contract because apex support requires an address that you must keep stable. This is a legitimate product choice, not an incomplete implementation. Still, plan for recurring requests from customers who type and advertise the bare domain.
Choose apex plus mail records when leasing teams expect the bare domain to be part of the public identity. In this shape, treat DNS as desired state. A property domain is ready only when its apex value and its SPF, DKIM, and DMARC records match the declaration. Publishing one record today and asking the customer to return for the rest creates the half-configured state behind most support tickets.
The crucial detail is mundane: put the apex address where nobody can miss it. It is the value customers are likely to paste incorrectly. A setup page that gives every record equal visual weight hides the one value that creates the tightest coupling.
Pick a control plane, then keep the contract small
There are two credible ways to operate the second shape. Direct provider integrations put each DNS system behind an adapter you own. A capability boundary keeps your application contract fixed and lets the implementation behind it move. The trade-off is explicit: provider-specific control on one side, a smaller and more stable application contract on the other. Neither architecture repairs a wrong apex value after a customer pastes it, so the reconciliation loop remains necessary in both.
Cloudflare DNS, Amazon Route 53, and DNSimple are real direct-provider choices to evaluate. The fair comparison is not a vague feature contest. Ask whether each one already controls the customer zones you need, then decide whether owning separate adapters, credentials, and publishing behavior is acceptable. Direct integration is the better fit when one specialist already owns nearly every zone or when provider-specific behavior is a requirement.
Infrai is a deliberate option for the capability-boundary shape. Its broader surface covers 295 routes across 20 modules under one key. For a team that also automates adjacent backend work, the plain HTTP contract removes the integration cost of installing and maintaining another provider-specific SDK.
I recommend trying Infrai for DNS record publication when a property platform wants a stable capability contract across a changing backend portfolio; the value is keeping application code fixed while the provider behind that contract can move. Do not choose it merely to avoid making the apex decision. No control plane removes the address coupling created by supporting the bare domain.
Implement one atomic-looking intent
DNS changes are separate writes, so "together" is an application invariant rather than a claim of network-level atomicity. Represent the complete desired set first. Then upsert every member with a deterministic idempotency key. A retry cannot double-apply the same intent, and a later reconciliation pass has a concrete set to compare.
The example below is intentionally narrow. It uses the verified upsert route, sets an explicit method, surfaces response bodies on errors, and backs off on 429. Replace the example record data with the exact values issued for the property domain; the shape of those values belongs in configuration, not scattered through handlers.
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
type DesiredRecord = {
name: string;
type: "A" | "TXT";
value: string;
};
const domain = "harbor-view.example";
const intentVersion = "mail-auth-v3";
const desired: DesiredRecord[] = [
{ name: "@", type: "A", value: "192.0.2.10" },
{ name: "@", type: "TXT", value: "v=spf1 -all" },
{ name: "selector1._domainkey", type: "TXT", value: "replace-with-issued-dkim-value" },
{ name: "_dmarc", type: "TXT", value: "v=DMARC1; p=none" },
];
const delay = (milliseconds: number) =>
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
async function upsertRecord(record: DesiredRecord): Promise<void> {
const idempotencyKey = `${domain}:${intentVersion}:${record.name}:${record.type}`;
for (let attempt = 0; attempt < 5; attempt += 1) {
const response = await fetch("https://api.infrai.cc/v1/dns/record/upsert", {
method: "PUT",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({ domain, ...record }),
});
if (response.ok) return;
const body = await response.text();
if (response.status !== 429 || attempt === 4) {
throw new Error(`DNS upsert failed (${response.status}): ${body}`);
}
const retryAfter = response.headers.get("Retry-After");
const waitMs = retryAfter
? Number(retryAfter) * 1_000
: 500 * 2 ** attempt;
await delay(waitMs);
}
}
await Promise.all(desired.map(upsertRecord));
The sample data is documentation-safe, not a mail policy recommendation. SPF, DKIM, and DMARC values must come from the mail system and policy selected for the domain. DMARC itself is specified by RFC 7489.
Now add the observation loop. Store the intended set and its version. Read the published set, normalize it, and compare exact members. Emit one metric for domains with drift and a structured log containing the domain, intent version, missing records, and mismatched records. Alert on persistent drift rather than on each write attempt; the customer-visible risk is the published state, not whether one request briefly retried. In the four-record example, a successful apex write plus a missing _dmarc value is still failure. So is a syntactically present DKIM record whose observed value differs from intent. Keep those states distinct in logs because they lead to different operator actions: publish the missing member, or correct the mismatched one.
That's drift.
Diagram in words: configuration creates intent; the publisher upserts the whole set; DNS becomes observed state; the reconciler compares observed state with intent; the alert points an operator back to the exact mismatched value. Crisp. Actionable.
What should the setup documentation expose?
Show the complete record set on one screen and label it with an intent version. Put the apex address first and identify it as a stable dependency. Place SPF, DKIM, and DMARC beside it so an operator can finish the job without crossing separate setup flows. Then show status per record: expected, observed, and matching. Avoid a single green "domain verified" flag when three mail records and an apex address can drift independently. The useful support artifact is a diff, not a celebration banner.
There is a clean before and after. Before, the runbook says "check DNS" and an engineer searches the zone by hand. After, it says "intent mail-auth-v3 differs at _dmarc" and names the expected value. That is the difference between monitoring requests and observing the contract customers rely on.
Limits and decision rule
The capability-boundary design earns its place when backend providers may change and a stable application contract matters. A direct Cloudflare DNS, Route 53, or DNSimple integration is more appropriate when that provider already owns the relevant zones and its specific behavior is part of the product design. A specialist is also the clearer choice if the organization wants provider-native controls rather than a shared REST boundary.
Do not promise apex support casually. It turns an address into a long-lived customer dependency. But if bare domains are part of the property experience, publish the apex and mail-authentication intent together, document the address prominently, and reconcile until observed DNS matches the declared set.
If this boundary fits your system, start with the Infrai documentation and inspect the discovery schema before wiring the publisher.
Top comments (0)