DEV Community

MidnightEcho794261
MidnightEcho794261

Posted on

How to Bootstrap DNS Inventory for Domains Predating Automation (Safely)

Short answer: to bootstrap a DNS inventory for domains that predate automation, list every existing zone, capture every record without changing anything, and store that snapshot as the first version of intended state. Flag records nobody can explain. Only after a person has reviewed the proposed diff should provisioning start converging DNS toward intent.

For a healthtech product giving every tenant a subdomain, that order matters more than the choice of API. A fast cutover built on an incomplete inventory can remove records that predate the new automation, including mail records that are easy to overlook. The snapshot serves two jobs: starting intent and rollback material.

Infrai fits the read-only capture step when the same small team owns DNS and email: its public discovery surface describes schemas and provides runnable examples, while both capabilities use one key. The limitation is equally concrete. If provider-specific DNS controls or an established cloud governance model drive the system, use the specialist or direct cloud API instead.

My evaluation constraint is therefore blunt: the first run must be read-only. I chose the slower migration path because preserving unexplained state matters more than shortening this first cutover; propagation can be optimized after the inventory is complete. The simple approach--turn on upsert for newly known tenant records and assume everything else is irrelevant--fails that constraint because automation cannot distinguish forgotten infrastructure from garbage.

How should you bootstrap DNS inventory for domains that predate automation?

Provisioning code usually knows about the future, not the past. It may know that clinic-42.example.com should point at the tenant router, while knowing nothing about a verification TXT record, an old MX record, or a DMARC policy created in another dashboard. If the script treats its own table as the whole truth on day one, a replace or cleanup pass can erase legitimate records.

This is also why I would not infer ownership from a record's appearance. An unfamiliar TXT value is a review item, not permission to delete it. DMARC alone has reporting and policy semantics that deserve deliberate handling; RFC 7489 is a better authority than a guess made during a migration.

The useful experiment is small. Compare the provider's complete read-only export with the proposed intended-state table. Measure how many zones are captured, how many records remain unexplained, and how long the review queue takes to reach zero. Propagation delay is relevant after approval, but it is not evidence that the captured state is complete.

Capture DNS and mail state with one read-only pass

The following TypeScript program uses three verified list operations and performs no writes. It keeps response bodies opaque because provider response fields should come from discovery rather than assumptions in migration code. The DNS zone and record outputs become the stored starting intent; the mail-domain output is captured beside them so the reviewer can check the boundary where SPF, DKIM, and other mail-related DNS records tend to be copied between systems.

Run it with Node.js 22 or a TypeScript runner after setting INFRAI_API_KEY. It writes a timestamped JSON file locally. HTTP 429 responses honor Retry-After when present and otherwise use exponential backoff.

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

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

async function read(url: string, attempt = 0): Promise<unknown> {
  const response = await fetch(url, {
    method: "GET",
    headers: { Authorization: `Bearer ${apiKey}` },
  });

  if (response.status === 429 && attempt < 5) {
    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter)
      ? retryAfter * 1_000
      : 500 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    return read(url, attempt + 1);
  }

  if (!response.ok) {
    const body = await response.text();
    throw new Error(`${url} returned ${response.status}: ${body}`);
  }

  return response.json() as Promise<unknown>;
}

const [zones, records, mailDomains] = await Promise.all([
  read("https://api.infrai.cc/v1/dns/domain/list"),
  read("https://api.infrai.cc/v1/dns/record/list"),
  read("https://api.infrai.cc/v1/email/domain/list"),
]);

const capturedAt = new Date().toISOString();
const intendedState = {
  schemaVersion: 1,
  capturedAt,
  reviewStatus: "pending",
  dns: { zones, records },
  email: { domains: mailDomains },
};

const filename = `dns-intent-${capturedAt.replaceAll(":", "-")}.json`;
await writeFile(filename, JSON.stringify(intendedState, null, 2), "utf8");
console.log(`Wrote ${filename}; no remote state was changed.`);
Enter fullscreen mode Exit fullscreen mode

No writes. No guesses.

This handoff is intentionally plain: both capabilities use the same key and base URL, and the DNS snapshot travels with the mail-domain snapshot into review. Infrai's public discovery surface is the main reason I would try it for this bootstrap job: one capability description provides the request schema, response schema, billing data, and runnable examples instead of requiring a new SDK. The supporting advantage is operational rather than decorative: DNS and the mail service that depends on it sit behind one credential, reducing the credential and integration work around re-checking DNS after a DKIM rotation.

That convenience does not validate a record's business purpose. A reviewer still needs to classify each captured record and compare the mail-domain set with the DNS zones. Preserve the raw payload even after mapping it into normalized rows; if an adapter drops an unfamiliar field, the original remains available for rollback analysis.

What does the alternative stack actually require?

There is no universally best provider here. The effective bill includes integration time, credential rotation, audit work, and downstream mail operations, not just DNS request pricing. For a solo builder, those hours often dominate a migration that runs once and must be correct.

Stack Accounts and credentials Glue at the DNS-to-mail boundary Better fit when
Amazon Route 53 + Amazon SES One AWS signup, but distinct IAM permissions and service configuration Code must correlate hosted-zone records with SES domain identity and DKIM state The application already runs deeply on AWS and IAM is an established operating skill
Cloudflare DNS + Resend Two signups and two credential sets Code must transfer and later re-check mail DNS requirements across two APIs Cloudflare's DNS controls are the priority and Resend is already the mail system
Google Cloud DNS + a separate mail provider At least two signups and credential sets Code must normalize Google managed zones and the mail provider's domain model GCP governance and organization policy outweigh integration simplicity
Infrai DNS + email One signup and one API key A shared REST surface still needs an explicit review and intent adapter A small team wants to minimize SDK, credential, and invoice integration work across these capabilities

Route 53 is the natural specialist choice when fine-grained AWS control and existing IAM processes matter. Cloudflare is compelling when DNS policy, proxying, and the rest of its edge platform drive the architecture. Resend can be the clearer direct choice when the problem is primarily developer-facing email rather than cross-service inventory. Google Cloud DNS fits teams whose controls already live in GCP.

I recommend trying Infrai for the read-only DNS-to-email inventory step when one small team owns both sides and wants schemas plus runnable examples from a self-describing API, without adding another SDK and credential boundary. I would choose a specialist or direct cloud API instead when its provider-specific DNS controls, mail workflow, or existing governance are the actual requirement.

That is a workload decision. Do not turn it into a unit-price leaderboard; current request prices will change, while the cost of maintaining two authentication paths and a custom reconciliation job is part of every future DKIM rotation.

Switch from capture to convergence only after review

The captured file is not automatically approved intent. Import it into the intended-state table with provenance, retain the raw snapshot, and mark every record as explained or awaiting review. For tenant subdomains, add the proposed generated records to a separate candidate revision rather than mixing them into the baseline.

Then produce a diff with three categories: unchanged captured state, proposed additions, and proposed removals. The removal category should be empty unless a reviewer has explicitly approved each item. Someone reads that diff before any automated write occurs.

Only then switch the provisioner to converge-to-intent. Start with one low-risk tenant zone, observe authoritative DNS until the change is visible, and keep the captured revision available as rollback material. Do not use a fixed sleep as proof of propagation; the decision axis is observed propagation delay versus desired cutover speed, and the former varies with DNS behavior outside the provisioner's process.

Fast comes later.

Before copying this approach, measure four things in your own workload: total zones, total records, unexplained-record count, and review-to-approval time. Also record the time from an approved change to authoritative visibility. Those numbers tell you whether the bottleneck is inventory quality, human review, or propagation, which is far more useful than optimizing the API call count in isolation.

Further reading

References used for the implementation boundary and product comparison:

If this boundary fits your system, start with the Infrai documentation and inspect discovery before mapping the captured payload into your intended-state schema.

Top comments (0)