Skip to content

Repository files navigation

We require all those who participate in this repo to agree and adhere to the Mozilla Community Participation Guidelines.

dns-scripts

Small utilities for checking DNS records.

Licensed under MPL-2.0. Contributing (especially adding a DNS provider)? See CONTRIBUTING.md.

Thundermail DNS checker — web version

https://thunderbird.github.io/dns-scripts/

The same checks as the verify_thundermail_dns.py CLI below, but from a web browser: enter a domain, optionally pick your DNS provider, and get pass/fail per record plus exactly what to enter for any that are missing. All lookups run client-side via DNS-over-HTTPS (Cloudflare or Google) — no server, nothing stored.

Run it locally with any static file server:

python3 -m http.server 8000   # then open http://localhost:8000/

Single source of truth: the record set, value templates, and per-provider remediation strings live in records.json, which is read by both the CLI and the web page (index.html + app.js). Add a record or a provider there once and both front-ends pick it up.

Setup

Managed with uv:

uv sync

verify_thundermail_dns.py

Verifies that a domain's DNS is configured for Thundermail: the MX record, the JMAP/CalDAV/CardDAV/IMAPS/submission SRV records, the SPF / MTA-STS / TLSRPT / DMARC TXT records, and the three DKIM CNAMEs.

uv run verify_thundermail_dns.py glamrocnamecheap.com

Exit status is 0 only when every expected record is present and correct, so it's safe to use in scripts or CI. One deliberate exception: the SRV weight is not compared. Priority, port and target must match exactly, but any weight passes, because weight only distributes load between competing targets at the same priority and Thundermail publishes a single target per service — while some panels (Plesk / METANET) offer only a fixed dropdown of weights in steps of 5, making the documented 1 unenterable. See #14. It queries 1.1.1.1 by default (override with --resolver or the DNS_RESOLVER environment variable) to avoid stale local caches. The resolver accepts an IP address or a hostname — e.g. point it at an authoritative nameserver to bypass public-resolver caching entirely:

uv run verify_thundermail_dns.py glamrocnamecheap.com --resolver dns1.registrar-servers.com

Point it at whichever nameserver is actually authoritative for the domain — check with dig NS <domain> first, since a registrar may run several nameserver sets and querying the wrong one just returns empty answers. For example, a domain hosted on Cosmotown's DNS is usually delegated to ndns1.cosmotown.com / ndns2.cosmotown.com (Cosmotown: What are Cosmotown's nameservers?):

uv run verify_thundermail_dns.py <domain> --resolver ndns1.cosmotown.com

A domain hosted on Bunny DNS is delegated to kiki.bunny.net / coco.bunny.net (bunny.net: DNS records):

uv run verify_thundermail_dns.py <domain> --resolver kiki.bunny.net

Fixing failures

Pass --provider to print, for each failing record, exactly what to enter in that DNS provider's control panel — including provider-specific quirks such as how the Host/Name field is written. Supported: namecheap, squarespace, cosmotown, bunny, spaceship, godaddy, ionos, ovh, hover, digitalocean, porkbun, metanet-plesk, generic. bunny.net's add-record form has a single Value field (no separate Priority/Weight/Port), so the bunny MX/SRV output puts the whole record string in Value; the Hostname field is left empty for the root (bunny.net: DNS records). spaceship is unverified — its field conventions were inferred from a zone-import screenshot and Spaceship's own Spacemail docs (a different record set), not confirmed against the live manual add-record form, so its headers flag this and the SRV host handling is uncertain (see RELEASE_NOTES.md). godaddy is also unverified — its field conventions come from GoDaddy's help docs plus a screenshot of the live SRV add-record form, not yet confirmed end-to-end on a live GoDaddy-hosted domain, so its headers flag this. GoDaddy is the one panel that splits the SRV _service._protocol label into separate Service and Protocol fields (the record Name is then @ for the root). ionos is also unverified — its field conventions come from IONOS's help docs plus a screenshot of the live SRV add-record form, not yet confirmed end-to-end on a live IONOS-hosted domain, so its headers flag this. IONOS also splits the SRV _service._protocol label, but its Protocol is a TCP/UDP/TLS dropdown (hard-coded to TCP, since every checked SRV record is _tcp), and its SRV form has both a Host name (record location) and a separate Points to (target). ovh is also unverified — its field conventions come from OVHcloud's help docs plus a screenshot of the live TXT/SPF add-entry wizard, not yet confirmed end-to-end (the MX/SRV wizards weren't screenshotted), so its headers flag this. OVH's quirks: the Sub-domain field is left empty for the root (not @), and FQDN targets need a trailing dot (OVH appends your domain otherwise), so the ovh MX/SRV/CNAME output ends targets with a .. hover is also unverified — its SRV field layout comes from a screenshot of the live Edit DNS Record form, but the MX/TXT/CNAME field labels come from Hover's Managing DNS Records at Hover docs (not a live add-record screenshot), so its headers flag this. Hover splits the SRV _service._protocol label into separate Service and Protocol fields (like GoDaddy/IONOS), but its optional Subdomain field is left empty for the root (not @ — the hover SRV output uses a blank subdomain, driven by a {srvsubhost} token); MX/TXT use @ for the root. digitalocean is also unverified — its SRV field layout comes from a screenshot of the live Create a record dialog, while the MX/TXT/CNAME field labels come from DigitalOcean's Create, Edit, and Delete DNS Records docs (not a live add-record screenshot), so its headers flag this. DigitalOcean keeps the whole SRV _service._protocol label in one Hostname field (it does not split it like GoDaddy/IONOS/Hover), uses @ for the root (MX/TXT), and appends your domain to any target entered without a trailing dot — so the digitalocean MX/SRV/CNAME output ends targets with a . (like ovh). This trailing-dot behaviour is confirmed live on digitalocean.example.com, whose SRV targets were entered without the dot and came back as mail.thundermail.com.digitalocean.example.com. porkbun is also unverified — its MX/TXT/CNAME field layout comes from Porkbun's KB article How to Add DNS Records and its SRV layout from the SRV record guide (screenshots, but not confirmed end-to-end on a live panel), so its headers flag this. Porkbun (Cloudflare-backed) uses a single Add-record dialog with a Type dropdown: the Host field is left blank for the root (not @), MX and SRV have a separate Priority field, and SRV keeps the whole _service._protocol label in one Host with Weight/Port/Target packed into the Answer field. Unlike ovh/digitalocean, targets do not need a trailing dot — confirmed live on porkbun.example.com, whose MX and DKIM CNAME targets came back verbatim (Porkbun stores them as entered). metanet-plesk is also unverified — it covers METANET's Plesk DNS panel (Websites & Domains → DNS-Einstellungen → Eintrag hinzufügen), whose SRV field layout is confirmed from a screenshot of the live form on a METANET-hosted zone, while the German MX/CNAME field labels come from Plesk's docs rather than a live add-record form, so its headers flag this (METANET: Plesk DNS-Verwaltung). The panel is German-language, so the emitted field labels are German with an English gloss (Zielhost (Target host)). Plesk's quirks: Domainname is left empty for the root (never @); it splits the SRV label into Service-Name and Protokoll like GoDaddy/IONOS/Hover but wants them without the leading underscore (imaps, tcp — the {bareservice}/{bareprotocol} tokens exist for this); Priorität and Relative Gewichtung are dropdowns rather than free text, and the weight dropdown only offers 0/5/10…50, so Thundermail's weight of 1 cannot be entered (hence the ignored-weight matching above, and #14); targets are stored verbatim with no trailing dot (Plesk adds the root dot itself — confirmed live); TXT values are auto-quoted by the panel, so they're emitted unquoted (like cosmotown); and nothing goes live until you click Aktualisieren (Update) on the pending-changes banner. METANET also sells a paid Premium Service: Individuelle DNS-Einstellungen where support enters the records for you — the emitted headers mention it as a fallback. Note that Cosmotown's customer panel has no SRV section, so the five SRV records can't be self-served — the cosmotown output routes you to Cosmotown support for those. See Cosmotown's docs for changing/saving DNS records, adding a host name, and updating the MX record.

uv run verify_thundermail_dns.py glamrocnamecheap.com --provider namecheap

For example, a missing DKIM CNAME prints:

• CNAME tm1._domainkey
  Namecheap → Advanced DNS → Add New Record → CNAME Record:
      Host:  tm1._domainkey
      Value: tm1.glamrocnamecheap.com.dkim.thunderhosted.com   (no trailing dot)

Example

Verifying Thundermail DNS for glamrocnamecheap.com (resolver 1.1.1.1)

MX:
  OK   @                                              10 mail.thundermail.com
SRV:
  OK   _jmap._tcp                                     0 1 443 mail.thundermail.com
  ...

Result: 13 passed, 0 failed.