# Checking propagation

When a change is live, how to know, and what to do before you press verify.

## What a successful write means

A write (`set_dns2`, `PUT`, `DELETE`) returns only after the change is saved at the authoritative nameservers: the servers the rest of the internet asks about your host.

- **Public resolvers** (the ones Google, Microsoft and everybody else use) usually see a new record within one to two minutes.
- If you **replaced** a record, a resolver that cached the old one can keep serving it until that record's TTL runs out (usually 3600 seconds; `get_dns` shows it).
- If a resolver asked for the name **before** the record existed, it may remember "no such record" for a while. That is why you don't verify until the record is live.

## Ask before you verify

1. Write the record.
2. Poll `dns_status` (Dynadot style) or `GET /api/v1/dns/status/{domain}` (Spaceship style) with the record's name, type and value, every 10 to 15 seconds.
3. When it says live, ask Google or Microsoft to verify.
4. If verification still fails, wait a few minutes and try again. Don't write the record again: it is already there.

`dns_status` asks each authoritative nameserver directly, without any cache in between, and says `yes` only when **every one** of them answers with the record.

It exists in two styles, Dynadot and Spaceship. Writing in Cloudflare, GoDaddy, Porkbun or Namecheap style? Call the Dynadot-style `dns_status` below with the same key: it only needs `key`.

## Dynadot style

```bash
curl https://api.maildns.io/api3.json \
  --data-urlencode key=KEY \
  --data-urlencode command=dns_status \
  --data-urlencode domain=advisor.example.com \
  --data-urlencode subdomain=@ \
  --data-urlencode record_type=txt \
  --data-urlencode value=google-site-verification=3J9tq0iV8pTt1yW5zKc2mE7aR4bN6hD0sF1gL8xQ
```

```json
{
  "DnsStatusResponse": {
    "ResponseCode": 0,
    "Status": "success",
    "Records": [
      {
        "Name": "advisor.example.com",
        "RecordType": "TXT",
        "Value": "google-site-verification=3J9tq0iV8pTt1yW5zKc2mE7aR4bN6hD0sF1gL8xQ",
        "Live": "yes",
        "Servers": {"ns1.example.net": "yes", "ns2.example.net": "yes"}
      }
    ]
  }
}
```

`subdomain=@` is the host itself; for DKIM use `subdomain=google._domainkey`, for DMARC `subdomain=_dmarc`. Full parameters: [`dns_status`](https://maildns.io/docs/dynadot.md#dns_status).

## Spaceship style

```bash
curl -G https://api.maildns.io/api/v1/dns/status/advisor.example.com \
  -H 'X-API-Key: KEY' -H 'X-API-Secret: SECRET' \
  --data-urlencode name=_dmarc \
  --data-urlencode type=TXT \
  --data-urlencode 'value=v=DMARC1; p=quarantine'
```

```json
{
  "items": [
    {
      "name": "_dmarc.advisor.example.com",
      "type": "TXT",
      "value": "v=DMARC1; p=quarantine",
      "live": false,
      "servers": {"ns1.example.net": true, "ns2.example.net": false}
    }
  ]
}
```

Full parameters: [Check records](https://maildns.io/docs/spaceship.md#check-records).

## A polling loop

```python
import os
import time
import requests

API = "https://api.maildns.io/api3.json"
KEY = os.environ["MAILDNS_KEY"]


def is_live(host, name, rtype, value):
    """name is relative to host: "@" for the host itself, "_dmarc", "google._domainkey"."""
    reply = requests.post(API, timeout=90, data={
        "key": KEY, "command": "dns_status", "domain": host,
        "subdomain": name, "record_type": rtype, "value": value,
    }).json()
    inner = next(iter(reply.values()))
    if str(inner.get("ResponseCode")) != "0":
        raise RuntimeError(inner.get("Error"))
    return bool(inner["Records"]) and all(r["Live"] == "yes" for r in inner["Records"])


def wait_live(host, name, rtype, value, timeout=900, every=15):
    deadline = time.time() + timeout
    while time.time() < deadline:
        if is_live(host, name, rtype, value):
            return True
        time.sleep(every)
    return False


if wait_live("advisor.example.com", "@", "txt",
             "google-site-verification=3J9tq0iV8pTt1yW5zKc2mE7aR4bN6hD0sF1gL8xQ"):
    print("live: verify it now")
else:
    print("not live after 15 minutes: check the record with get_dns")
```

If a record is not live after 15 minutes, something else is wrong: read the host back with `get_dns` and compare the value character by character (a `+` that arrived as a space is the usual cause).

## Check it yourself

`get_ns` returns the authoritative nameservers. Ask one of them directly with `dig`:

```bash
dig +short TXT advisor.example.com @ns1.example.net
dig +short TXT _dmarc.advisor.example.com @ns1.example.net
```

Without the `@server` part, `dig` asks your own resolver, which shows what the public sees (and may still have an old answer cached).
