Summary
At scale, MX record provisioning breaks in two predictable ways: records getting written twice when a retry fires too fast, and conflicting priority settings that cause routing failures that are hard to diagnose.
The guide covers the full MX record lifecycle, how priority works and how to script a failover swap, and a safe write function you can drop into any domain provisioning workflow.
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
# 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/1234567Priority 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.
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:
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.
What are the required fields for creating an MX record via the name.com API?
Every MX record creation request requires four fields: type (set to "MX"), host ("@" for the root domain or a subdomain string), answer (the FQDN of your mail server — IP addresses are rejected per RFC 5321), and priority (a unique integer per host). A ttl value is also recommended, with 300 as a sensible default for provisioning workflows that need fast propagation.
How do I prevent duplicate MX record errors when my provisioning script runs more than once?
Use a GET-before-POST pattern: fetch the current record set for the domain before creating anything. If a record with the matching answer and priority already exists, skip the POST. The safe_upsert_mx function in this article encodes all three branches — create, update, and skip — so calling it multiple times with the same arguments produces no side effects after the first successful run.
How does MX priority work, and what constraint does the name.com API add?
Per RFC 5321, sending mail transfer agents sort a domain's MX records by priority value and attempt delivery to the lowest-numbered record first — priority 10 is tried before priority 20. While the RFC permits duplicate priority values for load balancing, the name.com API enforces unique priority values per host. Plan your priority scheme before provisioning: duplicate priorities will be rejected.
How do I script a mail provider failover using the name.com API?
Keep both your primary (priority 10) and backup (priority 20) MX records in place at all times. To fail over, swap their priority values with two PUT requests: demote the primary to 20 and promote the backup to 10. The swap_mx_priority function in this article reads current state first and only fires PUTs if the swap hasn't already happened, making it safe to run repeatedly.
What's the difference between the name.com production and sandbox environments?
Production runs at https://api.name.com; sandbox runs at https://api.dev.name.com. The sandbox requires your username with a -test suffix (e.g., reseller123-test) and a separate sandbox API token. Domains registered in the sandbox are fully isolated from production and carry no charges. Always build and test against the sandbox before pointing your provisioning code at production.
