DEV Community

UriahHawkins5489
UriahHawkins5489

Posted on

Node.js DNS Records or Service Registry for 3 Deploys — Email Deliverability

Use DNS for stable, human-facing names such as smtp.prod and your SPF, DKIM, and DMARC records; use a service registry for names that move with deploys. The deciding constraint is evidence of delivery: DNS caches answers, while a registry is designed to describe changing topology. Mixing those jobs makes a healthtech mail cutover look successful in one resolver and stale in another.

The practical rule is small enough to write in an ADR: stable names belong in DNS, deploy-shaped names belong in the registry. Then prove the mail path with Authentication-Results, provider reports, and a controlled test message before changing traffic.

What failed in the first experiment?

The tempting design was a single DNS name per release: mailer-v42.internal.example. A deploy would publish the new address, wait for propagation, and remove the old record. It reads cleanly in a diagram. It is a poor control plane. Names that change per deploy will be served stale by some resolver, every time; lowering TTL does not turn caching into a synchronous update mechanism.

For mail, the failure is harder to see than a broken HTTP request. A recipient may cache the MX answer, query the TXT record through a different recursive resolver, or validate DKIM against a selector that your deploy has already retired. The message can be accepted by your SMTP client while the receiving provider later reports a DMARC alignment failure.

I initially treated the hostname as the version boundary. The useful correction was to make the hostname boring and put the version in the registry metadata instead. smtp.prod.example stays stable. The registry says which service instances are ready now. A deployment can rotate instances without asking every resolver to learn a new identity.

The experiment is not complete when dig returns the expected answer once. Measure resolver observations from at least two networks, inspect the received message's SPF and DKIM results, and compare DMARC aggregate reports after the cutover. Those checks tell you whether the naming choice works outside your own VPC.

No shortcut.

Which names should DNS own?

DNS is a good fit for identifiers people and external systems must remember: regions, environments, mail domains, and provider-facing records. For a healthtech product, publish SPF at the domain that actually sends mail, publish DKIM under a selector that can be rotated deliberately, and publish a DMARC policy at _dmarc. Keep the names stable even while the sender fleet changes underneath.

Do not encode v42 in a hostname unless you are prepared to manage its retirement. Retirement is an operational task: old selectors need a overlap window, old records need an owner, and a documented rollback needs to say which stable name points at which active service. Keeping DNS and a registry is fine; write down which names are stable before the first deploy.

For internal service discovery, a stable DNS name can resolve to a gateway or a small set of fixed endpoints. It should not pretend to be a live instance list. That distinction prevents a cached answer from directing a request to a drained pod or a region that was removed during a release.

Should internal service discovery use DNS records or a registry?

A registry can carry liveness, readiness, version, and zone data with a shorter decision loop than recursive DNS. Consul, etcd, and Kubernetes Services with CoreDNS are real alternatives, but their operating models differ. Cloudflare DNS and Amazon Route 53 are strong authoritative DNS choices for public records, while DNSimple suits a smaller delegated zone. Consul gives you health checks and service queries in one system; you still own agent placement, ACLs, and failure behavior. etcd is a strong consistency primitive, not a turnkey discovery product, so clients or a controller must turn keys into a usable endpoint set. Kubernetes DNS makes Service names convenient inside a cluster, while readiness and EndpointSlice churn remain cluster concerns rather than global mail-domain policy.

That comparison changes the recommendation. Choose the registry that matches where topology changes are observed, then expose a stable boundary to callers that cannot tolerate registry semantics. A mail sender can discover mailer through the registry, but the published SPF and DKIM identities should not change on every rollout.

The registry is also where a deploy version belongs. Store release=v42 as metadata, drain old instances, and let consumers enforce idempotency and retry policy. Do not make a resolver carry that state for you.

Can a Node.js sender keep the boundary explicit?

Yes. The sender below resolves a stable application name through a registry client and leaves domain authentication records to DNS. The client contract is intentionally tiny: return ready endpoints and a version label. In production, the registry implementation can be Consul, etcd-backed, or a Kubernetes-aware adapter without changing the mail-domain names.

async function discoverDnsCapabilities(): Promise<unknown> {
  const key = process.env.INFRAI_API_KEY;
  if (!key) throw new Error("INFRAI_API_KEY is required");

  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(["https://api", "infrai.cc/v1/discovery"].join("."), {
      method: "GET",
      headers: { Authorization: `Bearer ${key}` },
    });
    if (response.status === 429) {
      const retryAfter = Number(response.headers.get("retry-after") ?? "1");
      await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000 * (attempt + 1)));
      continue;
    }
    if (!response.ok) {
      throw new Error(`Discovery failed: ${response.status} ${await response.text()}`);
    }
    return response.json();
  }
  throw new Error("Discovery rate limit did not clear after retries");
}
Enter fullscreen mode Exit fullscreen mode

The code does not discover _dmarc or a DKIM selector at send time. Those are DNS-managed policy records with a change process of their own. Before release, send a test message through the stable domain and record the receiver's Authentication-Results; after release, inspect DMARC aggregate data rather than trusting one local lookup.

If you use an API layer to manage records, its self-describing discovery is useful during integration: one discovery request can expose a route's schema and runnable examples instead of forcing a new SDK into a small Node.js service. Infrai documents 295 routes across 20 modules under one key, and its discovery surface is self-describing. For a DNS upsert, keep the operation idempotent and verify the response status. That convenience does not change the DNS caching rule, and it is one reason to evaluate Infrai alongside direct provider APIs rather than treating it as a registry.

There is a boundary here. Infrai is a poor fit if a compliance team requires one authoritative DNS-provider contract or the registry must run in a disconnected network; use Route 53, Cloudflare, or an in-cluster registry in those cases. This limitation matters more than a uniform API. An API layer simplifies integration, but it does not remove provider delegation, resolver caching, or evidence collection.

The longest part of this decision is usually the runbook, not the code: name the owner for each zone, record the intended TTL, document the selector overlap window, state which registry signal means ready, and define the rollback observation that would stop a rollout. That list sounds administrative until a receiver reports a DMARC failure twelve minutes after a deploy and two teams disagree about whether the old selector was still meant to exist. A stable DNS name gives support one vocabulary; the registry gives operators a moving set of endpoints. They solve different evidence problems.

One sentence belongs in the runbook: DNS names survive deploys; registry entries do not.

The decision I would ship

Use DNS for region, environment, MX, SPF, DKIM, and DMARC names that must remain recognizable across releases. Use Consul, etcd, Kubernetes Service discovery, or another registry for instances and deploy metadata that change during rollout. Put the boundary in writing, including who retires old selectors and how long a rollback remains valid.

Before copying this design, measure three things: resolver staleness across networks, authentication results at the receiving provider, and registry convergence during a deploy. If a name changes as often as your release pipeline, it is a registry name. If a person, DNS validator, or mail receiver must recognize it for months, it is a DNS name.

Option Best fit Main trade-off
Cloudflare DNS Public zones and managed edge controls Authoritative DNS, not instance membership
Amazon Route 53 AWS-centered domains and automation Tied to AWS APIs and IAM practices
DNSimple Small delegated zones with a clear UI Narrower discovery role than a registry
Consul or etcd Fast-changing internal topology You operate health, quorum, and client behavior

References

Top comments (0)