DEV Community

Cover image for Domain, business email and DNS: the order to configure them so mail doesn't land in spam
AumCreate
AumCreate

Posted on

Domain, business email and DNS: the order to configure them so mail doesn't land in spam

A zone file that serves a website and a zone file that can send mail are the same file. That is why the usual failure looks like this: the site is live, an MX record got added, everything looks configured, and then the first invoice lands in a customer's spam folder.

Deliverability comes down to order more than to any single record. DMARC published before DKIM exists rejects legitimate mail. Two SPF records left behind by a mail migration return permerror, which is worse than having no SPF at all. A sending subdomain with no DMARC record of its own quietly inherits the parent policy.

Here is the sequence I use. Each step ends with a check that has to pass before the next one starts. Nothing below depends on a particular registrar or mail provider.

0. Write the sending map before touching DNS

List every system that will put your domain in a From: header, and give each one its own DKIM selector:

Sender Envelope domain Auth
People in the mail client example.com SPF + DKIM (s1)
App / transactional mail example.com DKIM (s2)
Newsletter platform mail.example.com DKIM (s3), own DMARC
Website contact form example.com DKIM (s2)

The map matters because selectors are per-sender, and you need all of them before you configure DMARC. Anything you forget here becomes the thing that breaks in step 6.

1. Register the domain and keep DNS in one place

Use the registrar's nameservers or delegate the zone to a DNS host, but pick one location and keep the whole setup there. Splits, where some records sit at the registrar and some at the hosting panel, are where "the record exists but nothing resolves" comes from.

Lower the TTL on the records you are about to create to 300. You will edit them several times; a 3600 TTL turns each edit into an hour of waiting.

2. Publish and verify the web records first

Do the boring part before mail, so a broken A record is never confused with a broken mail setup.

example.com.      300 IN A     203.0.113.10
example.com.      300 IN AAAA  2001:db8::10
www.example.com.  300 IN CNAME example.com.
Enter fullscreen mode Exit fullscreen mode
dig +short example.com A @1.1.1.1
dig +short example.com A @8.8.8.8
dig +short www.example.com CNAME @1.1.1.1
Enter fullscreen mode Exit fullscreen mode

No CNAME at the apex. If your DNS host offers ALIAS or ANAME, that is a different record type and it is fine; a literal CNAME on example.com is not.

If the web half is being handed to someone else, that is a separate job from the mail half, and it is worth knowing which one you are buying. AumCreate's Setup service ($399, what's included) installs a WordPress template on the client's own hosting, replaces every page with the client's content, sends a preview link with one round of revisions, and goes live within five working days of the material being complete. Domain and hosting stay in the client's name and are paid to the third party. The records in this article still have to be published by whoever owns the zone — a site handover does not touch mail.

3. Create MX records only after the mail host exists

The MX target has to be a hostname that already accepts mail for the domain, and its own A record has to resolve.

example.com.  300 IN MX 10 mx1.mailhost.example.
example.com.  300 IN MX 20 mx2.mailhost.example.
Enter fullscreen mode Exit fullscreen mode
dig +short example.com MX @1.1.1.1
Enter fullscreen mode Exit fullscreen mode

Two MX records at the same priority is a coin flip per delivery attempt. Different priorities give you a fallback instead.

If you run your own MTA on a VPS, the PTR for the IP has to resolve forward to the same hostname you use in HELO. The PTR is set at the hosting provider, not in your zone, so start that request now — it is the one item in this list you cannot fix from the zone file.

4. SPF: one record, at the apex, under ten lookups

example.com.  300 IN TXT  "v=spf1 include:_spf.mailhost.example include:_spf.newsletter.example -all"
Enter fullscreen mode Exit fullscreen mode

The rules that break people:

  • Exactly one v=spf1 record per name. Two TXT strings starting with v=spf1 is a permerror, not a merge. When you migrate, delete the old record in the same change that adds the new one.
  • The ten-DNS-lookup limit counts include, a, mx, ptr, exists and redirect. ip4 and ip6 are free. Nested includes count everything they pull in, which is where the limit actually bites.
  • -all means everything else is unauthorized. ~all is a soft fail you use during a migration. Pick one on purpose.
  • SPF is evaluated against the envelope sender (MAIL FROM, falling back to HELO), not the From: header. A perfect record for the wrong domain does nothing for DMARC alignment.

Check with dig +short example.com TXT. If the string appears twice, stop and fix it.

5. DKIM for every sender, verified before DMARC goes on

The provider generates a key pair. You publish the public half as a TXT record at <selector>._domainkey.example.com.

s1._domainkey.example.com. 300 IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."
Enter fullscreen mode Exit fullscreen mode
  • Use 2048-bit keys.
  • A long p= value exceeds 255 characters. In raw zone text you split it into quoted strings that the resolver concatenates; most control panels do this for you, and if you paste a key into a panel that does not, you get a truncated key.
  • Verify the record resolves and is complete before anything depends on it:
dig +short s1._domainkey.example.com TXT @1.1.1.1 | wc -c
Enter fullscreen mode Exit fullscreen mode

Compare the length against the key the provider shows you. A truncated p= fails silently — mail still goes out, just unsigned.

Then send a real message and read the Authentication-Results header at the receiving end. You want dkim=pass with d= equal to your domain, not the provider's.

6. DMARC: start at none, read the reports, then enforce

_dmarc.example.com. 300 IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com; adkim=r; aspf=r; fo=1"
Enter fullscreen mode Exit fullscreen mode

p=none tells receivers to evaluate and report without acting on the policy. Leave it there through a couple of weeks of real traffic, including the newsletter and the form mail.

Then move in this order, a few days apart:

  1. p=quarantine; pct=25, then pct=50, then pct=100.
  2. p=reject.
  3. Only then tighten to adkim=s; aspf=s if everything is genuinely aligned.

Two things that catch people here:

  • A subdomain without its own _dmarc record inherits the organizational policy, including sp=, which defaults to p=. Bulk mail sent as mail.example.com with a From: on that subdomain needs either alignment or its own policy record.
  • rua has to point at a mailbox that exists and that someone reads. The reports arrive as XML attachments — ugly, and the only way to see which sender is failing.

dig +short _dmarc.example.com TXT. It has to be a TXT record; a CNAME at _dmarc resolves at some resolvers and fails at others.

7. What comes after mail already flows

Not required for deliverability, but cheap once the zone is clean:

  • MTA-STS: TXT at _mta-sts.example.com with v=STSv1; id=<timestamp>, plus the policy file served over HTTPS at https://mta-sts.example.com/.well-known/mta-sts.txt. That host needs a valid certificate, so do this after TLS is settled.
  • TLS-RPT: TXT at _smtp._tls.example.com with v=TLSRPTv1; rua=mailto:tls@example.com.
  • BIMI last, and only with a VMC. Skip it until DMARC has been at p=reject for a while.

8. The failures that show up weeks later

  • A recipient forwards your mail. SPF breaks at the forwarder; DKIM survives if the body was not rewritten. This is the main reason DKIM has to be in place before enforcement.
  • A sender is retired and its include stays in SPF. You keep authorizing an IP range you no longer control.
  • The newsletter moves to a new provider with a new selector and nobody publishes the matching DKIM record. DMARC keeps passing on SPF until the day alignment slips.
  • TTL stays at 300 forever. Harmless, but raise it once you are done, or a slow resolver somewhere will hold your setup-time mistakes longer than you would like.

The check to run before calling it finished

check() {
  for ns in 1.1.1.1 8.8.8.8 9.9.9.9; do
    printf '%-26s @%-8s ' "$2 $1" "$ns"
    dig +short "$1" "$2" @"$ns" | tr '\n' ' '
    echo
  done
}

check example.com MX
check example.com TXT
check _dmarc.example.com TXT
check s1._domainkey.example.com TXT
Enter fullscreen mode Exit fullscreen mode

If all three resolvers return the same answers, the zone is consistent and the rest is sending real mail and reading the headers at the other end.

Top comments (0)