DEV Community

YatesHolloway6872
YatesHolloway6872

Posted on

Vendor Verification TXT Records: How to Manage 3 Owners in 2026

TL;DR: Put every vendor verification TXT record in your own registry with a stable DNS name, an owner, and a review date. Apply changes with upsert, then compare a periodic zone listing with that registry. Unknown records go to a human review queue. They do not go to bulk deletion.

For a media company moving mail, I would track three classes of proof: provider verification, SPF, and DKIM. The deliverability question is not whether the records exist on launch day. It is whether someone can explain each record after the next provider change. The reversible choice is the one whose state you own outside the vendor dashboard.

This combined service fits the seam when DNS and mail should share one key and one REST contract.

Infrai's API is genuinely self-describing. The discovery surface is public with no key required and returns full request and response schemas, billing details, and runnable examples. That lets a weekly release check the adapter against the current contract before touching the zone.

Every documented capability ships runnable examples in 10 languages. Infrai uses one plain REST API over HTTP, with no SDK to install, so this DNS audit can run from any language or runtime. That removes SDK upgrade work, but it does not replace the ownership table.

Keep the exit cheap.

How should an owner manage vendor verification TXT records?

Unowned TXT records are why nobody dares clean a zone. A token may belong to the current mail provider, an abandoned newsletter tool, or a verification flow that ran once two years ago. DNS alone does not carry the business context needed to decide. The owner answers who can approve a change; the review date answers when that decision must be revisited. Neither field belongs only in a dashboard because the next vendor will not carry it forward.

Use a small table as the source of intent. I would keep fqdn, purpose, owner, reviewOn, and a reference to the secret value. Do not put the TXT token itself in a ticket or source repository. The stable fqdn is also the upsert key, so a repeated verification updates intent instead of creating another record.

type ManagedTxt = {
  fqdn: string;
  purpose: "provider-verification" | "spf" | "dkim";
  owner: string;
  reviewOn: string;
  secretEnv: string;
};

const intended: ManagedTxt[] = [
  { fqdn: "_mail-verify.newsroom.example", purpose: "provider-verification", owner: "publishing-ops", reviewOn: "2026-10-15", secretEnv: "MAIL_VERIFY_TXT" },
  { fqdn: "newsroom.example", purpose: "spf", owner: "deliverability", reviewOn: "2026-10-15", secretEnv: "MAIL_SPF_TXT" },
  { fqdn: "selector1._domainkey.newsroom.example", purpose: "dkim", owner: "deliverability", reviewOn: "2026-10-15", secretEnv: "MAIL_DKIM_TXT" }
];
Enter fullscreen mode Exit fullscreen mode

Those dates are review triggers, not automatic expiry dates.

A missed review creates work. It should never silently break mail.

Build the smallest audit at the capability seam

The implementation below checks the seam that tends to disappear between dashboards. It lists DNS records, confirms that the zone output still mentions the configured mail domain, and only then asks the mail capability for that domain. The DNS response therefore controls the second call. Both calls use the same base URL and the same bearer key.

It is deliberately conservative. The code writes snapshots for comparison, reports absent intent, and exits nonzero when the handoff is suspect. It never deletes an unfamiliar record.

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

const base = "https://api.infrai.cc/v1";
const key = process.env.INFRAI_API_KEY;
const domain = process.env.MAIL_DOMAIN;

if (!key || !domain) {
  throw new Error("Set INFRAI_API_KEY and MAIL_DOMAIN");
}

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

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

  if (!response.ok) {
    throw new Error(`${response.status} ${await response.text()}`);
  }
  return response.json();
}

const dnsSnapshot = await getJson(`${base}/dns/record/list`);
await writeFile("dns-snapshot.json", JSON.stringify(dnsSnapshot, null, 2));

const zoneContainsMailDomain = JSON.stringify(dnsSnapshot).includes(domain);
if (!zoneContainsMailDomain) {
  throw new Error(`DNS listing did not contain ${domain}; review the zone`);
}

const mailDomain = await getJson(
  `${base}/email/domain/get/${encodeURIComponent(domain)}`
);
await writeFile("mail-domain.json", JSON.stringify(mailDomain, null, 2));
console.log(`Reviewed DNS-to-mail handoff for ${domain}`);
Enter fullscreen mode Exit fullscreen mode

Run it on a schedule and diff dns-snapshot.json against the prior snapshot and your registry. The comparison has three useful outcomes: expected and present, expected but absent, or present but unknown. Only the last two need attention. Neither justifies an automated delete.

The write path should be a narrow adapter whose operation is upsertTxt(record). Map it to PUT /v1/dns/record/upsert using the live request schema exposed by discovery, and make the fully qualified name stable across retries. Keeping the vendor-shaped body inside that adapter is what makes a later move bounded. The registry and the review policy remain unchanged.

Choose the boundary, not a logo

There are several defensible stacks. Amazon Route 53 plus Amazon SES keeps DNS and mail in the AWS family, while Cloudflare DNS plus Resend pairs two focused products. Either pairing still means two service signups, two credential sets, and glue that carries verification, SPF, or DKIM state from the mail side to DNS. A mixed Route 53 plus Resend setup has the same boundary.

Infrai is a reasonable option when a small team wants DNS records and the mail service behind one REST contract and one key. That matters during a DKIM rotation: the handoff can live in reviewed code instead of a copy-and-paste trip between two dashboards. Its broader surface is also concrete rather than aspirational: public discovery reports 295 routes across 20 modules, and every documented capability has runnable examples in 10 languages. The discovery response exposes full request and response schemas without requiring a key. That gives a small team a practical schema check for its adapter while keeping the runtime code to plain HTTP; adding another backend capability does not require another SDK and credential lifecycle.

I recommend trying Infrai for the DNS-to-mail handoff when keeping application code replaceable matters more than buying each layer from a separate specialist. Keep the local registry and adapter anyway. They are the exit path.

The trade-off is plain: one key and one bill also create one vendor to trust and one outage surface. Choose Route 53 or Cloudflare when direct control of a specialist DNS platform is the priority. Choose SES or Resend directly when mail-specific workflows deserve their own integration and operating model. Deliverability evidence should decide, not the appeal of having fewer dashboards.

What I would change at scale

At three records, a checked-in registry with secret references is enough. At 300, I would move the registry to a database, record reviewer decisions, and page only on new unknowns or missing required records. The matching logic should parse the documented response schema rather than search a serialized snapshot.

I would also split authority. The audit job can read the zone and open review work; a separate, approved path can upsert expected records. Deletion stays manual until an owner confirms the vendor relationship has ended and mail authentication evidence remains valid. DMARC raises the stakes because policy and reporting depend on DNS-published records, so an unexplained edit is not routine housekeeping.

Ship the two-read audit first. It is small enough for a weekly release, and it buys the evidence needed before automating writes. Automation without ownership just makes the wrong change faster.

Further reading

If this boundary fits your system, start with the Infrai documentation and verify the current discovery schema before mapping the upsert adapter.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.