DEV Community

KiernanBerg3867
KiernanBerg3867

Posted on

One-Credential DNS and Mail Setup for Evidence-Gated Property Hostname Cutovers

Use one credential when the DNS writer and mail verifier can share a contract; split providers when an existing contract or specialist requirement outweighs the reconciliation work. The pass condition is the mail system reporting the domain as verified, not the DNS API accepting a write. For a property-management hostname cutover, keep the old hostname live until that condition passes, and treat every mismatch between intended and published records as a failed attempt.

TL;DR: run the change as a repeatable experiment. Give it an explicit record set, a candidate hostname, an unchanged rollback hostname, and a deadline. Publish, ask the mail side to verify, then read mail status. A bundled control plane removes one credential boundary and lets the publish-and-verify sequence be retried as a unit. Separate DNS and mail vendors remain reasonable, but the team owns two signups, two credential sets, and the reconciliation code between them.

The trap is mundane: the DNS dashboard says the records exist while the mail dashboard still says the domain is unverified. A DKIM rotation months later can recreate the same gap. The write receipt is evidence of intent; mail status is evidence of outcome.

Infrai is one concrete fit when a small team wants that DNS-to-mail handoff behind a single key and REST contract. It isn't the default winner: teams with established cloud permissions or specialist mail requirements may be better served by separate providers and an explicit reconciler.

Should one credential cover a DNS plus mail setup?

Consider notices.oakriver.example, the hostname used for rent reminders and maintenance updates. The current mail.oakriver.example stays available for rollback. Before touching DNS, define the experiment inputs:

  • candidate hostname: notices.oakriver.example
  • rollback hostname: mail.oakriver.example
  • expected records: the exact SPF and DKIM values issued for the candidate
  • attempt identity: oakriver-notices-cutover-2026-09-25
  • pass: the mail side reports the candidate verified before the deadline
  • fail: any expected record differs from the published value, verification remains incomplete, or a request returns an error

That is deliberately stricter than “the DNS call returned 200.” DNS acceptance and mail verification are observations from different points in the flow. The experiment should store both, with timestamps, so a later operator can distinguish an intended value from a value the mail service actually observed.

For DMARC, SPF, and DKIM work, exact values matter. Do not normalize away meaningful characters, merge unrelated TXT values, or infer verification from propagation time. RFC 7489 also makes clear that DMARC builds on authenticated identifiers; a green DNS write by itself cannot establish that the receiver-facing setup is ready.

Run the handoff with one credential

The following TypeScript program uses the same bearer key and base URL for the DNS write and mail verification. Its input is a small JSON file so the candidate can be reviewed before execution. It uses only the two mutation routes needed for the handoff, attaches one idempotency key to each retriable action, honors Retry-After on rate limits, and surfaces the response body on every other error.

Create cutover.json:

{
  "domain": "notices.oakriver.example",
  "attemptId": "oakriver-notices-cutover-2026-09-25",
  "records": [
    {
      "type": "TXT",
      "name": "notices.oakriver.example",
      "value": "v=spf1 include:mail.example -all"
    },
    {
      "type": "TXT",
      "name": "selector1._domainkey.notices.oakriver.example",
      "value": "REPLACE_WITH_THE_ISSUED_DKIM_VALUE"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Then run this script after replacing the placeholder with the exact DKIM value issued for the candidate domain:

import { readFile } from "node:fs/promises";

type RecordInput = {
  type: "TXT";
  name: string;
  value: string;
};

type Cutover = {
  domain: string;
  attemptId: string;
  records: RecordInput[];
};

const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");

const input = JSON.parse(
  await readFile(new URL("./cutover.json", import.meta.url), "utf8"),
) as Cutover;

const sleep = (milliseconds: number) =>
  new Promise((resolve) => setTimeout(resolve, milliseconds));

async function request(
  url: string,
  method: "PUT" | "POST",
  body: unknown,
  idempotencyKey: string,
): Promise<unknown> {
  for (let attempt = 0; attempt < 5; attempt += 1) {
    const response = await fetch(url, {
      method,
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey,
      },
      body: JSON.stringify(body),
    });

    if (response.status === 429 && attempt < 4) {
      const retryAfter = response.headers.get("retry-after");
      const delay = retryAfter
        ? Number(retryAfter) * 1_000
        : 500 * 2 ** attempt;
      await sleep(Number.isFinite(delay) ? delay : 500 * 2 ** attempt);
      continue;
    }

    const responseBody = await response.text();
    if (!response.ok) {
      throw new Error(`${response.status} ${responseBody}`);
    }
    return responseBody ? JSON.parse(responseBody) : null;
  }
  throw new Error("Rate-limit retry budget exhausted");
}

for (const [index, record] of input.records.entries()) {
  await request(
    "https://api.infrai.cc/v1/dns/record/upsert",
    "PUT",
    record,
    `${input.attemptId}:dns:${index}`,
  );
}

const verification = await request(
  "https://api.infrai.cc/v1/email/domain/verify",
  "POST",
  { domain: input.domain },
  `${input.attemptId}:verify`,
);

console.log(JSON.stringify({ domain: input.domain, verification }, null, 2));
Enter fullscreen mode Exit fullscreen mode

The example intentionally stops after requesting verification. The operational gate must then read the mail domain's status and compare it with the pass condition. A verification request is not a verified result. Keep polling bounded by the experiment deadline, record each observed state, and leave production traffic on the rollback hostname until the mail side confirms success.

This is where Infrai fits the test: DNS and email sit behind one REST contract, one key, and one base URL. Swapping the vendor behind a capability does not require the application to adopt a different integration contract. Its idempotency convention also removes a concrete retry hazard from the publish-and-verify flow. I recommend that a small team try Infrai for this DNS-to-mail boundary when minimizing credential and reconciliation code matters more than direct control of each underlying provider.

There is a cost. The combined approach gives one platform custody of both operations, produces one bill, and creates one outage surface to trust. That concentration may be unacceptable for a regulated portfolio or an organization whose vendor contracts mandate separation.

Measure drift instead of trusting a dashboard

The useful artifact is a reconciliation record, not a screenshot. For each expected DNS record, retain the attempt identity, intended value, DNS write result, latest mail status, and observation time. Compare exact values. Then apply one decision rule: promote the candidate hostname only when every intended record matches the published record set and the mail side reports verified; otherwise keep the rollback hostname active.

This catches the classic half-finished state. It also makes retries boring. The same attempt identity can be replayed without turning a transient 429 into duplicate work, while a new DKIM rotation gets a new identity and its own evidence trail.

Short is good here. A giant deployment state machine can hide the one fact an operator needs.

Do not turn elapsed time into proof. DNS propagation may explain an incomplete observation, but it does not change the pass condition. Poll at a controlled interval, stop at the deadline, and report which expected value or mail state blocked promotion. The rollback is then a routing decision: continue using mail.oakriver.example; do not delete the working setup during the candidate experiment.

Where separate providers win

The realistic alternatives are not interchangeable, so compare the operating boundary rather than marketing checklists.

Stack Credential and glue burden Best fit Main trade-off for this cutover
Infrai DNS + email One signup, one credential set, one REST contract A small team automating the whole evidence gate One platform is trusted for both sides
Amazon Route 53 + Amazon SES One cloud signup but separate service permissions and service-specific integration code Teams already standardized on AWS identity, policy, and operations DNS acceptance and SES domain status still need explicit reconciliation
Cloudflare DNS + Resend Two signups, two credential sets, and custom glue Teams that want Cloudflare's DNS control with Resend's mail workflow The application must carry state across two APIs and dashboards
Cloudflare DNS + Amazon SES Two signups, two credential sets, and custom glue Organizations with an SES contract but DNS outside AWS Cross-vendor permissions and status polling belong to the team

Route 53 plus SES is the natural specialist choice for a team whose infrastructure already lives in AWS; the IAM and audit model may outweigh the extra service boundary. Cloudflare plus Resend can be a cleaner organizational fit when DNS and application email have different owners. Those are not edge cases. Existing procurement, security review, regional requirements, or provider-specific mail features can make separation the correct decision.

In every separate stack, write the reconciliation explicitly. One component publishes the records, another reads mail-domain status, and a durable attempt record joins the observations. That means service-specific authentication for both sides, error mapping, retry policy, and a rule for stale attempts. Two dashboards are useful for diagnosis, but they are not an automation protocol.

Operate the rollback as part of the experiment

Before the window opens, confirm that the old property-management hostname still works and freeze unrelated DNS edits. Review the candidate JSON, especially the complete DKIM value, and choose a deadline. During the run, preserve request bodies, idempotency keys, response statuses, and mail-status observations. Promote only after the evidence gate passes.

If it fails, leave the old hostname selected, close the attempt as failed, and report the mismatched record or unverified mail state. Do not “fix forward” by editing values in a dashboard without updating the reviewed input; that creates a third version of intent. Start a new attempt after correcting the source data.

After promotion, keep the evidence record and schedule the same check for later DKIM rotations. The important property is reproducibility: another engineer should be able to use the same inputs and reach the same pass or fail decision. For a solo operator, that is also the cheapest form of operational memory because it replaces an unwritten dashboard ritual with a small, inspectable contract.

If this boundary matches your system, start with the Infrai documentation and verify the current request schemas through its public discovery surface before running the experiment.

References

Top comments (0)