Clicking through a DNS management panel to add an MX record takes five minutes. Doing it reliably for every domain your platform provisions during user signup is a different problem entirely.

Consider a SaaS platform that provisions custom domains for each new customer at the moment of registration. With 50 signups per day, you're looking at 50 DNS operations, each of which needs to happen within seconds of domain registration, in the correct order, with no duplicate priority values and no "human in the loop". The UI workflow breaks at that scale.

Race conditions between registration and record creation are a common failure mode. A domain registration request returns success, your script fires a DNS record creation 200ms later, and the registrar's internal systems haven't finished propagating the new zone yet. You receive an error on the API call. MX records are structured data with a defined schema: a type, a host, an answer (FQDN), a TTL, and a priority value. Treating them as API resources with a full lifecycle makes the provisioning problem manageable.

MX Record Payload: Fields, Constraints, and What the API Rejects

Every MX record creation request requires four fields:

type: must be "MX" — host: "@" for root domain or subdomain string — answer: FQDN of your mail server (the API rejects IP addresses) — priority: required integer, must be unique across all MX records on the same host.

That last constraint causes the most problems in bulk provisioning scripts. If you run your provisioning function twice for the same domain and both runs attempt to create a priority-10 record, the second call fails. Your function needs to GET the existing record set first and check for conflicts before writing.

Full Lifecycle: Create, Read, Update, Delete

Bash
# Create
curl -u "reseller123-test:your_sandbox_token" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"host":"@","type":"MX","answer":"mail.example.com","ttl":300,"priority":10}' \
  https://api.dev.name.com/core/v1/domains/mycoolstartup.io/records

# Read
curl -u "reseller123-test:your_sandbox_token" \
  https://api.dev.name.com/core/v1/domains/mycoolstartup.io/records

# Update (requires record ID from Create/Read response)
curl -u "reseller123-test:your_sandbox_token" \
  -X PUT \
  -H "Content-Type: application/json" \
  -d '{"answer":"mail.newprovider.com","type":"MX","host":"@","priority":10,"ttl":300}' \
  https://api.dev.name.com/core/v1/domains/mycoolstartup.io/records/1234567

# Delete
curl -u "reseller123-test:your_sandbox_token" \
  -X DELETE \
  https://api.dev.name.com/core/v1/domains/mycoolstartup.io/records/1234567

Priority Semantics and Scripted Failover

RFC 5321 defines MX priority behavior: when a mail transfer agent delivers a message, it sorts the destination domain's MX records by priority value and attempts delivery to the lowest-numbered record first. A record with priority 10 gets tried before one with priority 20.

The practical provisioning pattern: a primary record at priority 10 pointing to your main mail server, and a backup at priority 20 pointing to a secondary. To fail over, promote the backup to priority 10 and demote the original to priority 20. Both records stay in place; you update only their priority values.

python
import requests

def swap_mx_priority(domain, primary_answer, backup_answer, auth):
    base = "https://api.dev.name.com/core/v1"

    resp = requests.get(f"{base}/domains/{domain}/records", auth=auth)
    resp.raise_for_status()
    records = resp.json().get("records", [])

    mx = {r["answer"]: r for r in records if r["type"] == "MX"}

    for answer in (primary_answer, backup_answer):
        if answer not in mx:
            raise ValueError(f"MX record not found for answer: {answer!r}")

    if mx[primary_answer]["priority"] != 10:
        print("No need to swap, configuration unchanged")
        return

    updates = [
        (mx[primary_answer], 20),
        (mx[backup_answer], 10),
    ]

    for record, new_priority in updates:
        r = requests.put(
            f"{base}/domains/{domain}/records/{record['id']}",
            auth=auth,
            json={
                "host": record["host"],
                "type": "MX",
                "answer": record["answer"],
                "ttl": record.get("ttl", 300),
                "priority": new_priority,
            }
        )
        r.raise_for_status()

    print("Swapped MX records between primary and backup")

Wiring MX Config into a Provisioning Pipeline

The name.com API supports domain registration and DNS record creation as sequential calls in the same workflow. Your provisioning code POSTs to /core/v1/domains to register the domain, then immediately POSTs to /core/v1/domains/{domainName}/records to attach the MX entry.

The idempotency logic that makes this safe in production runs as a GET-then-decide pattern. Before creating any MX record, fetch the current record set for the domain, evaluate what exists, and choose to create, update, or skip:

python
def safe_upsert_mx(domain, answer, priority, auth, ttl=300, host="@"):
    base = "https://api.dev.name.com/core/v1"

    resp = requests.get(f"{base}/domains/{domain}/records", auth=auth)
    resp.raise_for_status()
    records = resp.json().get("records", [])

    mx = [r for r in records if r["type"] == "MX"]

    # Skip if already correct
    for r in mx:
        if r["answer"] == answer and r["priority"] == priority:
            print(f"MX record for {answer} already correct (priority {priority}), skipping")
            return

    record_body = {"host": host, "type": "MX", "answer": answer,
                   "ttl": ttl, "priority": priority}

    # Update if answer exists with wrong priority
    for r in mx:
        if r["answer"] == answer:
            resp = requests.put(
                f"{base}/domains/{domain}/records/{r['id']}",
                auth=auth, json=record_body
            )
            resp.raise_for_status()
            print(f"Updated MX record for {answer} to priority {priority}")
            return

    # Create new record
    resp = requests.post(f"{base}/domains/{domain}/records",
                         auth=auth, json=record_body)
    resp.raise_for_status()
    print(f"Created new MX record for {answer} with priority {priority}")

# Ensure a primary MX record exists at priority 10
safe_upsert_mx(
    domain="mycoolstartup.io",
    answer="mail.example.com",
    priority=10,
    auth=("reseller123-test", "your_sandbox_token")
)

If this function runs twice, the second execution reads that the record already sits at the correct priority and exits without writing. That GET-before-write pattern is what makes automated provisioning safe to retry.