DNS Record Guard

Checks a DNS edit or delete against the whole zone before saving, so a removed MX or broken SPF doesn't quietly stop company email.

StopsDeleted MX record stopped company email

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/dns-record-guard.json

The files land in your project. They're yours to edit.

What it catches

  • Removing MX records that receive company email
  • SPF senders dropped, or SPF pushed past the 10 DNS-lookup limit
  • A DKIM selector removed while DMARC enforces
  • A CNAME at the zone root or beside other records, and names left pointing at nothing
  • Long TTLs, with "lower TTL first" and a countdown until caches clear

Props

DnsRecordGuard props
PropTypeDefault
zone* (required)

The zone's root domain, e.g. "acme.com".

string—
records* (required)

Every record in the zone as it is now, so the guard can see what depends on the change.

DnsRecord[]—
change* (required)

The edit or delete being confirmed.

DnsChange—
onConfirm* (required)

Applies the change. Return a promise to show a pending state; a rejection is caught.

(change: DnsChange) => void | Promise<void>—
onCancel

Called when the user cancels.

() => void—
onLowerTtl

Saves a lower TTL on the record now, so the real change later spreads in minutes. Hides the fix when omitted.

(record: DnsRecord, ttl: number) => void | Promise<void>—
safeTtl

TTL (seconds) the one-tap fix sets.

number300
slowTtl

Records cached longer than this (seconds) get the "lower TTL first" suggestion.

number3600
title

Heading; receives the change and record.

(change: DnsChange, record: DnsRecord) => React.ReactNode(c, r) => `${c.kind === "delete" ? "Delete" : "Save"} ${r…
confirmLabel

Confirm button text.

(change: DnsChange, breaking: number) => string(c, breaking) => (breaking ? `${c.kind === "delete" ? "De…
pendingLabel

Confirm button text while onConfirm runs.

string"Saving…"
cancelLabel

Cancel button text.

string"Cancel"
safeText

Shown when no problems are found.

string"Nothing else in this zone depends on this record."
spreadText

Text estimating how long the change takes to spread.

(ttl: string) => string(ttl) => `Takes up to ${ttl} to reach everyone: resolvers…
lowerLabel

Text for the lower-TTL-first button.

(ttl: string) => string(ttl) => `Lower TTL to ${ttl} first`
waitText

Text shown while waiting for old caches to expire.

(oldTtl: string, time: string) => string(oldTtl, time) => `TTL lowered. Caches holding the ${oldT…
clearedText

Shown once old caches have expired.

(ttl: string) => string(ttl) => `Old caches have cleared. This change now spread…

* required. Also accepts the native props of <section> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Dialog content is labelled by its title
  • Findings are aria-live="polite" and say "Breaks:" or "Risk:" in text
  • The TTL countdown is aria-live="polite"; its progress bar is aria-hidden

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Validate zone changes at your DNS provider and keep a record of the previous zone so changes can be rolled back.

  • Checks only the records you pass; it doesn't query live DNS
  • Can't know which third-party senders still need SPF or DKIM

Rules

Rules last verified:

Informational. Checked the SPF limit of 10 lookup terms and that a CNAME label may hold no other data. DKIM/DMARC escalation rules were not checked against a standard. Copied components don't update themselves, so re-check after rule changes (see updating a component).

What it stops

Deleted MX record stopped company email

Also known as

dns · mx record · spf · dkim · dmarc · cname · ttl · cloudflare dns

Field type

Dependencies

Pairs well with