# Tell every customer which DNS record is wrong

Your customers publish DNS for their own domains, and some of it goes wrong. Diagnostics reads their zone live and returns each problem as a finding: which record, what is in its way, and the exact change that fixes it.

[Get started](/signup)
[Documentation](https://docs.aiinbx.com/guides/domains#diagnostics)

Example: verifying mail.oakline.com returns verified\_at: null with every record verified except DKIM, which is pending. Diagnostics on the same domain returns one breaking finding, record-doubled:DKIM: the DKIM value is published at aibx.\_domainkey.mail.oakline.com.oakline.com because the DNS panel appended the zone to a full name, and the fix is to set the record's name to just aibx.\_domainkey.mail. The finding's evidence is the record as read from the zone.

Live lookups

## Read from their nameservers, not a cache

A resolver keeps serving an old answer until its TTL runs out, which is exactly when a customer has just fixed it. Lookups ask the zone's own nameservers first, and fall back to an ordinary resolver only when those have no answer or cannot be reached. Nothing is stored, so every call reads the zone again.

[Live lookups](https://docs.aiinbx.com/guides/domains#diagnostics)

Findings

## Worst first, with the fix attached

Breaking means mail fails. Risk means it works today and will cost mail later. Tip means nothing is wrong. Each finding names the record it concerns, when there is one, and quotes the lines it was read from.

[Findings](https://docs.aiinbx.com/guides/domains#diagnostics)

Events

## Verified once, and watched after

domain.verified fires when the DKIM key verifies. Domains are rechecked in the background, so one whose key disappears loses verified\_at, sends domain.lost, and comes back by itself once the record does.

[Events](https://docs.aiinbx.com/guides/domains#staying-verified)

Records

## The records, in the shape a DNS panel asks for

Each record comes with its purpose, type, name, value and TTL. In the console they also download as a zone file of what is still outstanding, marking any record the customer has to delete first, since an import only adds.

[Records](https://docs.aiinbx.com/guides/domains#the-records)

Every rule

## Why a customer's domain won't verify

Most failures are not a missing record but a record in the wrong shape, or something else in the zone standing in its way. These are the rules diagnostics runs, and the id each finding comes back with.

Some customers never need to touch DNS at all: an address under [your own wildcard domain](/features/wildcard-domains) is live the moment it is created.

### Any record

-   Published at the name with the domain on it twice, because the DNS panel appended the zone to a full name: record-doubled:<purpose>, breaking.

-   The right value with quotation marks stored around it, so it no longer matches character for character: value-mangled:<purpose>, breaking.

-   A CNAME already sits at the name, and a name with an alias can hold nothing else: cname-conflict:<purpose>, breaking.

### DKIM

-   The domain's DMARC already says reject or quarantine while the key is not published, so mail is refused or filed as spam until it is: dmarc-enforced-early, breaking.

### SPF

-   Two SPF records on one name: receivers give up on both. Breaking on the return path, a risk on the root domain: spf-multiple:<name>, breaking.

-   More than ten DNS lookups in the record's own mechanisms, before counting what each include pulls in: spf-lookups:<name>, risk.

-   The record ends in +all, which lets any server send as the domain: spf-open:<name>, risk.

-   DMARC set to aspf=s, which refuses the bounces subdomain; mail still passes on DKIM alone: dmarc-strict-spf, risk.

### DMARC

-   More than one DMARC record, so receivers ignore all of them: dmarc-multiple, breaking.

-   No p= tag, so the whole record is thrown away: dmarc-invalid, risk.

-   No rua address, so nobody receives the aggregate reports: dmarc-no-reports, tip.

### MX, for receiving

-   Another MX at the same or a lower priority number, so mail goes there however green our record reads: mx-precedence, breaking.

-   The MX points at our inbound in a different region from the one the domain is set up in: mx-other-region, breaking.

-   An MTA-STS policy in enforce mode that does not list our server. In testing mode it is a tip: mta-sts-mismatch, breaking.

-   A wildcard CNAME at \*.domain, where a wildcard's MX has to go: wildcard-cname, breaking.

-   Another provider receives the domain's mail today, and publishing our MX moves it: mx-foreign, risk.

-   An MTA-STS record announces a policy file that nothing serves: mta-sts-unreachable, risk.

-   Old mail servers left behind ours as backups: mx-backup, tip.

### The zone

-   No nameservers answer for the domain, so it is unregistered, expired or only just delegated: zone-missing, breaking.

-   A \* record answers for names nobody set up, so a missing record can read as a wrong one: wildcard-zone, risk.

-   Under a wildcard, the apex keeps receiving wherever it does today: apex-keeps-mail, tip.

-   Some lookups did not answer in time, so the list may be short: lookups-incomplete, tip.

Set up

## A domain check in your onboarding flow

Step 1, domains.create: the records to show the customer.

```
const domain = await aiinbx.domains.create({
  name: "mail.oakline.com",
})

for (const record of domain.records ?? []) {
  show(record.purpose, record.type, record.name, record.value)
}
```

Step 2, domains.verify: when they press Check.

```
const domain = await aiinbx.domains.verify(domainId)

if (!domain.verified_at) {
  const { data } = await aiinbx.domains.diagnostics(domainId)
  return data.map(({ severity, title, fix }) => ({ severity, title, fix }))
}
```

Step 3, your webhook handler: live, and when it stops being.

```
if (event.type === "domain.verified") {
  await customers.markSendingReady(event.data.domain_id)
}
if (event.type === "domain.lost") {
  await customers.askToRestoreDns(event.data.domain_id)
}
```

## Questions

### Why is my domain not verifying?

Usually because a record is published in the wrong shape rather than not at all: at a name with the domain on it twice, with quotation marks stored in the value, next to a CNAME, or beside a second SPF or DMARC record. The diagnostics endpoint looks for each of these in the live zone and returns the record it found and the change to make.

### Can a domain have two SPF records?

No. A name may hold one SPF record, and a receiver that finds two treats the check as failed rather than combining them. Keep one, move the include: parts of the other into it, and delete the extra. Diagnostics reports it as spf-multiple, with both records quoted.

### Do I have to change the SPF record on my root domain?

No. The SPF record AI Inbx asks for goes on the return-path subdomain, bounces.<domain>, so the root domain's SPF stays as it is. Diagnostics still reads it and flags a duplicate, more than ten lookups or +all as a risk, because those break the check for everything else that sends as the domain.

### Why does a DNS checker show my record while verification says it is missing?

Either the checker is reading a cached answer, or it found the value somewhere other than the exact name that is looked up. AI Inbx asks the zone's own nameservers first, so a record corrected a minute ago reads as it is now, and a value buried one zone deeper is reported as record-doubled.

### How long does DNS verification take?

Usually minutes and occasionally hours, depending on the DNS host. A record not found yet reads pending for 48 hours after the domain is added, and missing after that. Rather than polling POST /domains/{id}/verify in a tight loop, subscribe to the domain.verified webhook, which fires when the DKIM key verifies.

### Does a verified domain mean SPF and DMARC are correct?

No. verified\_at is set when the DKIM key verifies, which is what sending needs. It does not certify SPF, DMARC or the inbound MX record; each record carries its own state, and diagnostics reports what is wrong with the rest of the zone.

Keep reading

## More on building with email

-   [Send email APISend email from your domain or your users' own Gmail and Outlook with one API call. Opt-outs and pacing are checked first, and replies land on the thread.ThreadingMailboxesPacingSuppressions](/solutions/send-email-api)
-   [Inbound email APIReceive email in your app over an API. Point an MX record at AI Inbx or connect Gmail and Outlook, and every message arrives as a signed, parsed JSON webhook.ThreadingAttachmentsMailboxesWildcard domains](/solutions/inbound-email-api)
-   [Email open trackingTrack email opens and clicks per domain or per send. Apple privacy prefetches and security scanner clicks arrive flagged as bots, each with its reason.Threading](/solutions/email-open-tracking)

-   [Wildcard domainsOne domain, a subdomain per customer.](/features/wildcard-domains)

[All solutions](/solutions)

## Give your product an inbox. The hard parts are already handled.

[Get started](/signup)
