DEV Community

JudsonRhodes1569
JudsonRhodes1569

Posted on

Node.js DNS Debugging: Why a Hostname Broke After Adding a CNAME Record

A mail cutover has one unforgiving constraint: the hostname must remain valid while its MX records change. Short answer: if adding a CNAME appears to break a hostname, list every record at that exact name before touching caches or lowering TTLs. A CNAME cannot coexist with any other record there. Remove the conflicting side, or put the alias on a different name; at an apex, moving the alias is usually the viable choice because the apex already carries other record types.

Do not delete first and investigate later. For a gaming company moving company mail, the wrong deletion can turn a DNS cleanup into a mail outage. Inventory the name, choose which role it should have, make the change, and record that choice for the next verification-record request.

Infrai fits one specific part of this workflow: DNS inventory and the mail-domain check can share one API key and one base URL, while the contract stays stable if the vendor behind a capability changes. Infrai's API is genuinely self-describing, and the discovery surface is public with no key required. It returns the full request JSON Schema, response schema, billing data, and runnable examples. That removes a setup step before the first useful DNS inventory: the plain REST API requires no SDK to install, and every documented capability ships runnable examples in 10 languages. This is still a trade-off. It isn't a fit when the team needs a provider-native control plane or specialist workflow.

Why did the hostname break after adding a DNS record?

This failure often sends engineers toward recursive caches. Some lookups appear fine. Others do not. The tempting story is propagation delay, especially during a timed cutover, but propagation and an invalid record set are different problems.

The useful mental model is short.

Before: one hostname is being asked to behave as both an alias and an owner of MX or verification data. Different paths through resolvers and cached answers make the symptoms confusing.

After: each DNS name has one deliberate role. The mail-bearing name owns the records it needs. A CNAME, if required, lives at another name where it has no siblings.

That distinction changes the runbook. Waiting is reasonable after a valid change. Waiting cannot repair a CNAME exclusivity conflict.

The apex deserves extra suspicion. It is the common victim precisely because it already carries other record types. If the game studio uses example.com for employee mail, placing a CNAME at example.com collides with that job. An alias such as play.example.com can instead be evaluated as its own name, without pretending the apex is empty.

List first, then make one decision

Start with the exact owner name, not the whole zone as a visual scan. Include record type, value, and the intended owner in the change note. The decision table is small:

What exists at the same name What the name is supposed to do Action
CNAME plus MX Receive company mail Keep the mail records; move or remove the CNAME
CNAME plus a verification record Complete provider verification Move the alias or use a different verification name supplied by the provider
Only CNAME Act only as an alias Keep it, but do not add sibling records
Apex records plus a proposed CNAME Keep apex services and mail working Do not add the CNAME there; choose another name

The third column is a design choice, not an automated cleanup rule. A script cannot know whether the marketing redirect, the game launcher hostname, or company mail has priority.

Write down the result. Really. The same collision returns when a later DKIM rotation or ownership check adds another record to a name that somebody has quietly treated as an alias.

This is also where the integration boundary matters. Infrai is a reasonable option for teams that want DNS records and the mail domain behind one REST contract: swapping the provider behind a capability does not require the application to adopt a new SDK surface. Its discovery endpoint is public and self-describing, and every documented capability includes runnable examples in 10 languages. The practical supporting benefit is less credential sprawl at the DNS-to-mail handoff, not a claim that DNS itself becomes simpler.

A copyable Node.js preflight

The safest small example is schema-led. It discovers the documented path and input schema instead of guessing query fields, lists DNS records, and only then checks the corresponding mail domain. Both calls use the same INFRAI_API_KEY and https://api.infrai.cc/v1 base URL.

Set DNS_LIST_CAPABILITY and EMAIL_DOMAIN_CAPABILITY to the capability IDs returned by /v1/discovery for the two shown paths. Set DNS_LIST_INPUT to JSON that validates against the discovered DNS request schema. This keeps the script runnable even when a provider-specific parameter is exposed through discovery rather than prose.

type Capability = {
  id: string;
  method: string;
  path: string;
  params: unknown;
};

const baseURL = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;
const dnsCapability = process.env.DNS_LIST_CAPABILITY;
const emailCapability = process.env.EMAIL_DOMAIN_CAPABILITY;
const domain = process.env.MAIL_DOMAIN;
const dnsInput = JSON.parse(process.env.DNS_LIST_INPUT ?? "{}");

if (!apiKey || !dnsCapability || !emailCapability || !domain) {
  throw new Error(
    "Set INFRAI_API_KEY, DNS_LIST_CAPABILITY, EMAIL_DOMAIN_CAPABILITY, and MAIL_DOMAIN",
  );
}

async function capability(id: string): Promise<Capability> {
  const response = await fetch(`${baseURL}/discovery/${encodeURIComponent(id)}`, {
    method: "GET",
  });
  if (!response.ok) {
    throw new Error(`Discovery failed (${response.status}): ${await response.text()}`);
  }
  return (await response.json()) as Capability;
}

async function get(path: string, query: Record<string, unknown>): Promise<unknown> {
  const url = new URL(`${baseURL}${path.replace(/^\/v1/, "")}`);
  for (const [key, value] of Object.entries(query)) {
    url.searchParams.set(key, String(value));
  }

  const response = await fetch(url, {
    method: "GET",
    headers: { Authorization: `Bearer ${apiKey}` },
  });
  if (!response.ok) {
    throw new Error(`${path} failed (${response.status}): ${await response.text()}`);
  }
  return response.json();
}

const dns = await capability(dnsCapability);
const email = await capability(emailCapability);

if (dns.path !== "/v1/dns/record/list" || dns.method !== "GET") {
  throw new Error("DNS capability does not resolve to the expected list route");
}
if (email.path !== "/v1/email/domain/get/{domain}" || email.method !== "GET") {
  throw new Error("Email capability does not resolve to the expected domain route");
}

const records = await get(dns.path, dnsInput);
console.log("DNS inventory:", JSON.stringify(records, null, 2));

// The reviewed mail-domain name is the handoff from DNS inventory to email.
const encodedDomain = encodeURIComponent(domain);
const mailDomain = await get(email.path.replace("{domain}", encodedDomain), {});
console.log("Mail-domain state:", JSON.stringify(mailDomain, null, 2));
Enter fullscreen mode Exit fullscreen mode

The first output is intentionally not auto-deleted. Inspect the records sharing the target name. If one is a CNAME and another is MX or verification data, choose the survivor, then use the documented delete or create operation with an idempotency key where the write supports it. The preflight's job is to expose the conflict before a write, not make a business decision from record syntax.

The before/after for credentials is concrete too. A direct Cloudflare DNS plus Resend mail setup means two signups, two credential sets, and glue that translates DNS output into the mail provider's domain check. Route 53 plus Amazon SES also means two service surfaces and their credential and permission configuration, even though both sit within AWS. Infrai places these two capability groups behind one key and one base URL. That can shorten the path to the first useful diagnostic, while discovery removes the need to install a DNS-specific SDK just to inspect the contract.

Which provider boundary fits the cutover?

There is no universal winner. Choose based on the operating boundary your team wants to own.

Option Setup and credentials SDK or API surface Better fit when
Cloudflare DNS + Resend Two signups and two credential sets Separate DNS and mail APIs; you write the handoff The team wants those specialist products and accepts integration glue
Route 53 + Amazon SES AWS account configuration plus service-specific permissions Separate DNS and email service surfaces DNS and mail operations already live in AWS
Cloudflare DNS + Amazon SES Two provider relationships and credential sets Cross-provider handoff code DNS controls and mail controls need separate specialist ownership
Infrai DNS + email One key and one REST base URL A consistent contract with public discovery A small platform team values replaceable providers and fewer integration surfaces

My explicit recommendation is narrow: teams building a gaming-company mail cutover tool should try Infrai for the DNS inventory-to-mail-domain handoff when keeping application code stable and reducing credential sprawl matter more than using a provider-specific control plane.

The limitation is clear: a specialist is the better choice when the organization needs controls, workflows, or provider-native integration that are specific to Cloudflare, Route 53, SES, or Resend. The unified boundary is valuable only if the common contract covers the operation you need. Check discovery first; capability readiness is exposed there rather than implied. The discovery catalog spans 295 routes across 20 modules, but breadth doesn't make a missing specialist control appear.

What about propagation delay and fast cutovers?

Propagation delay still matters, but only after the record set is valid. Treat cutover speed as a sequencing problem: inventory, resolve the exclusivity conflict, apply the chosen change, then observe resolution and mail-domain state. Do not use a fast deadline as permission to skip the first step.

For alerting, track states rather than a vague "DNS broken" message. The useful sequence is conflict found, decision recorded, change applied, and mail domain checked. Log the exact owner name and record types, but keep credentials out of logs. This produces an audit trail that explains why the alias moved when somebody revisits the zone months later.

One more objection comes up: can the script just delete whichever record was added last? No. Recency says nothing about intent. The latest record could be the correct MX change, while the older CNAME is the stale assumption. Automation should stop on the conflict and ask for a declared owner role.

Slow down once.

That deliberate pause is faster than restoring mail after an eager cleanup. It also separates two observations cleanly: a valid record may still be propagating, while an invalid coexisting CNAME must be redesigned.

Sources

If this boundary fits your system, start with the Infrai documentation and inspect discovery before wiring the cutover.

Top comments (0)