Set up MX, SPF, DKIM and DMARC for a domain
8 steps · Checked against the API code 3 November 2026
A guide does one task from start to finish. Every endpoint, with all its fields and cases, is in the API reference.
Mailboxes on Hostup web hosting? Connect a domain’s email to Hostup web hosting writes all these records in one call. For any other mail provider, add the records it gives you to the domain’s DNS zone at Hostup, one record per request. Then remove the old MX records.
Before you start
Section titled “Before you start”- The requests use your key from
$HOSTUP_API_KEY(Quickstart). The key needs the scopewrite:dns. It coversread:dnsas well. Keys and scopes - The values from your mail provider: the MX hosts with their priorities, the SPF
include:, the DKIM name with its key and the DMARC policy you want. - The domain uses Hostup’s nameservers, for example
primary.ns.hostup.seandsecondary.ns.hostup.se. Other mail servers see the records only once it does. Change a domain’s nameservers shows how.
The examples use a provider whose MX hosts are mx1.mail.example.net and mx2.mail.example.net. Put your provider’s values in their place.
1. Find the DNS zone
Section titled “1. Find the DNS zone”Ask for the zone by the domain name. Send the domain itself, not a subdomain.
curl "https://cloud.hostup.se/api/v2/dns-zones?name=example.com" \ -H "Authorization: Bearer $HOSTUP_API_KEY"{ "data": [ { "id": "zone_01hxa3b4c5d6e7f8g9h0j1k2m3", "name": "example.com", "status": "active", "isDnsOnly": false, "kind": "linked" } ], "hasMore": false, "nextCursor": null}Keep the zone ID. Every record call below uses it.
export ZONE_ID="zone_…" # data[0].id aboveIf data is empty
Section titled “If data is empty”The account has no DNS zone with that name. The same empty list comes back when the key is limited to other DNS zones. For a domain registered with Hostup that uses Hostup’s nameservers, reading the domain’s DNS creates the zone. Point a domain at your server shows the two requests.
2. Read the current records
Section titled “2. Read the current records”List DNS zone records returns every record in the zone.
curl "https://cloud.hostup.se/api/v2/dns-zones/$ZONE_ID/records" \ -H "Authorization: Bearer $HOSTUP_API_KEY"{ "zone": { "id": "zone_01hxa3b4c5d6e7f8g9h0j1k2m3", "name": "example.com", "status": "active", "recordCount": 3, "…": "…" }, "records": [ { "id": "drr_01hxa3b4c5d6e7f8g9h0j1k2m4", "type": "MX", "name": "example.com", "value": "mail.example.com", "ttl": 3600, "priority": 5 }, { "id": "drr_01hxa3b4c5d6e7f8g9h0j1k2m5", "type": "TXT", "name": "example.com", "value": "v=spf1 ip4:192.0.2.10 ~all", "ttl": 3600 }, { "id": "drr_01hxa3b4c5d6e7f8g9h0j1k2ma", "type": "TXT", "name": "_dmarc.example.com", "value": "v=DMARC1; p=none", "ttl": 3600 } ]}Note the IDs of three kinds of record. Later steps use them:
- the MX records, which step 4 removes
- the TXT record whose value starts with
v=spf1, which step 5 changes - a TXT record named
_dmarc.example.com, which step 7 changes
3. Add the new MX records
Section titled “3. Add the new MX records”Send one request per MX host to Create a DNS record. A name of @ means the domain itself. priority is required for MX records. Lower numbers are tried first.
curl -X POST "https://cloud.hostup.se/api/v2/dns-zones/$ZONE_ID/records" \ -H "Authorization: Bearer $HOSTUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "MX", "name": "@", "value": "mx1.mail.example.net", "priority": 10 }'{ "id": "drr_01hxa3b4c5d6e7f8g9h0j1k2m6", "type": "MX", "name": "example.com", "value": "mx1.mail.example.net", "ttl": 3600, "priority": 10, "warnings": [ { "code": "same_name_mx_records", "records": [ { "id": "drr_01hxa3b4c5d6e7f8g9h0j1k2m4", "type": "MX", "name": "example.com", "value": "mail.example.com", "priority": 5 } ], "…": "…" } ]}Send the second host the same way with its own priority (mx2.mail.example.net with 20).
value is a hostname that resolves, never an IP address. A host in your own zone, such as mail.example.com, needs its A or AAAA record in the zone first.
warnings with same_name_mx_records lists the MX records that were there before. Mail can still go to them until step 4 removes them.
If email forwarding is on
Section titled “If email forwarding is on”The MX request answers 400 mx_record_blocked_email_forwarding. Email forwarding receives the domain’s mail itself. While it is on, the API refuses MX records at @. To use your provider, turn forwarding off with Turn domain email forwarding on or off. That needs the domain ID and the scopes read:domains, write:email and write:domains.
curl "https://cloud.hostup.se/api/v2/domains?name=example.com" \ -H "Authorization: Bearer $HOSTUP_API_KEY"{ "data": [ { "id": "dom_01hxa3b4c5d6e7f8g9h0j1k2m3", "name": "example.com", "…": "…" } ], "hasMore": false, "nextCursor": null}export DOMAIN_ID="dom_…" # data[0].id aboveThen turn forwarding off. From then on, mail to the domain is no longer forwarded.
curl -X PATCH "https://cloud.hostup.se/api/v2/domains/$DOMAIN_ID/email-forwarding" \ -H "Authorization: Bearer $HOSTUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"enabled": false}'{ "enabled": false, "…": "…"}List the records again (step 2). Then send the MX requests again.
To keep forwarding, leave it on and skip steps 3 and 4. SPF, DKIM and DMARC records can be added while forwarding is on.
4. Remove the old MX records
Section titled “4. Remove the old MX records”Delete each old MX record with Delete a DNS record. Put its id from step 2 in RECORD_ID:
export RECORD_ID="drr_…" # an old MX record's id from step 2curl -X DELETE "https://cloud.hostup.se/api/v2/dns-zones/$ZONE_ID/records/$RECORD_ID" \ -H "Authorization: Bearer $HOSTUP_API_KEY"The answer is 204 with no body. A record that is already gone also answers 204. Repeat this for every old MX record.
If the mail was on Hostup web hosting
Section titled “If the mail was on Hostup web hosting”An old MX such as mail.example.com, as in the example, often means the mailboxes were on a Hostup web hosting account. That account may still accept mail for the domain itself. Set its email routing to remote. The hosting then never accepts mail for the domain. These calls need the scopes read:services, write:hosting and console:services.
Find the hosting account. primaryDomain is each account’s main domain. For an addon domain, List hosting domains shows which account holds it.
curl -s "https://cloud.hostup.se/api/v2/shared-hosting" \ -H "Authorization: Bearer $HOSTUP_API_KEY" \ | jq -c '.data[] | {id, primaryDomain}'export ACCOUNT_ID="acct_…" # id of the account that holds the domaincurl -X PUT "https://cloud.hostup.se/api/v2/shared-hosting/$ACCOUNT_ID/email-routing" \ -H "Authorization: Bearer $HOSTUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com", "mode": "remote"}'{ "id": "eroute_01hxa3b4c5d6e7f8g9h0j1k2m3", "domain": "example.com", "mode": "remote", "detectedMode": null}Get email routing for a domain shows the mode before or after. auto follows the MX records. local delivers the domain’s mail on the hosting server.
5. Set the SPF record
Section titled “5. Set the SPF record”SPF is one TXT record on the domain itself. Its value starts with v=spf1. Keep a single SPF record per domain.
If the domain has an SPF record
Section titled “If the domain has an SPF record”Change the record from step 2 with Update a DNS record. Keep the senders that still send mail for the domain, such as a web server. Add your provider’s include:.
export RECORD_ID="drr_…" # the SPF record's id from step 2curl -X PUT "https://cloud.hostup.se/api/v2/dns-zones/$ZONE_ID/records/$RECORD_ID" \ -H "Authorization: Bearer $HOSTUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "TXT", "name": "@", "value": "v=spf1 ip4:192.0.2.10 include:_spf.mail.example.net ~all", "ttl": 3600 }'{ "id": "drr_01hxa3b4c5d6e7f8g9h0j1k2m5", "type": "TXT", "name": "example.com", "value": "v=spf1 ip4:192.0.2.10 include:_spf.mail.example.net ~all", "ttl": 3600}PUT replaces the whole record. Without ttl the TTL goes back to 3600 seconds. Use the id in the answer from now on. Update a DNS record says when it changes.
If there is no SPF record yet
Section titled “If there is no SPF record yet”Create it. Send the value without quotes. The API checks the SPF syntax and refuses a broken value with 400.
curl -X POST "https://cloud.hostup.se/api/v2/dns-zones/$ZONE_ID/records" \ -H "Authorization: Bearer $HOSTUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "TXT", "name": "@", "value": "v=spf1 include:_spf.mail.example.net ~all" }'6. Add the DKIM record
Section titled “6. Add the DKIM record”Your provider gives a selector and a public key. The selector is a short name such as s1. The record name is the selector followed by ._domainkey.
curl -X POST "https://cloud.hostup.se/api/v2/dns-zones/$ZONE_ID/records" \ -H "Authorization: Bearer $HOSTUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "TXT", "name": "s1._domainkey", "value": "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAexamplekey" }'{ "id": "drr_01hxa3b4c5d6e7f8g9h0j1k2m9", "type": "TXT", "name": "s1._domainkey.example.com", "value": "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAexamplekey", "ttl": 3600}- Send the key as one value without quotes, even when it is longer than 255 characters. The API splits long values into DNS strings itself. Quoted pieces copied from a zone file are joined into one value.
- Some providers give DKIM as a CNAME record. Send
"type": "CNAME"with the same name and the provider’s hostname asvalue. A CNAME cannot share its name with any other record. The API answers 409 when one is already there.
7. Set the DMARC record
Section titled “7. Set the DMARC record”DMARC is one TXT record at _dmarc. Its value must start with v=DMARC1. The API refuses anything else. The examples use p=none, which only asks receivers for reports. Move to p=quarantine or p=reject once the reports show that your mail passes.
Keep a single DMARC record per domain. The API accepts a second _dmarc record with a different value. Receivers then apply no DMARC policy at all.
If the domain has a DMARC record
Section titled “If the domain has a DMARC record”Change the _dmarc.example.com record from step 2 with Update a DNS record, as in step 5.
export RECORD_ID="drr_…" # the DMARC record's id from step 2curl -X PUT "https://cloud.hostup.se/api/v2/dns-zones/$ZONE_ID/records/$RECORD_ID" \ -H "Authorization: Bearer $HOSTUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "TXT", "name": "_dmarc", "value": "v=DMARC1; p=none; rua=mailto:[email protected]", "ttl": 3600 }'{ "id": "drr_01hxa3b4c5d6e7f8g9h0j1k2ma", "type": "TXT", "name": "_dmarc.example.com", "ttl": 3600}If there is no DMARC record yet
Section titled “If there is no DMARC record yet”Create it.
curl -X POST "https://cloud.hostup.se/api/v2/dns-zones/$ZONE_ID/records" \ -H "Authorization: Bearer $HOSTUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "TXT", "name": "_dmarc", "value": "v=DMARC1; p=none; rua=mailto:[email protected]" }'8. Check the result
Section titled “8. Check the result”Read the MX records again with a type filter.
curl "https://cloud.hostup.se/api/v2/dns-zones/$ZONE_ID/records?type=MX" \ -H "Authorization: Bearer $HOSTUP_API_KEY"{ "zone": { "id": "zone_01hxa3b4c5d6e7f8g9h0j1k2m3", "name": "example.com", "status": "active", "recordCount": 2 }, "records": [ { "id": "drr_01hxa3b4c5d6e7f8g9h0j1k2m6", "type": "MX", "name": "example.com", "value": "mx1.mail.example.net", "ttl": 3600, "priority": 10 }, { "id": "drr_01hxa3b4c5d6e7f8g9h0j1k2m7", "type": "MX", "name": "example.com", "value": "mx2.mail.example.net", "ttl": 3600, "priority": 20 } ]}Only your provider’s hosts should be left. Send ?type=TXT to see the SPF, DKIM and DMARC records.
What happens next
Section titled “What happens next”A record you create, change or delete is in the zone when the API answers. There is no job to follow. None of these calls sends an e-mail or costs money.
Mail servers that looked up the old MX records can keep using them until the old TTL has passed. Keep the old mailboxes until then.
Before each record change the API saves a copy of the zone. List DNS snapshots and List DNS history for a domain show what changed.
Next: Change DNS records on many domains at once
Errors you may hit
Section titled “Errors you may hit”- 400
mx_record_blocked_email_forwarding: email forwarding is on for the domain. Turn it off as in step 3. Then add the MX records again. - 400
invalid_request: a field is wrong.errorspoints at it when the API can tell, for example a missingpriorityon an MX record or a TTL outside 60 to 86400. Other refusals have noerrorsand do not say which check failed. Then check:- The MX host resolves.
- No other MX record at that name uses the same priority.
- The SPF and DMARC values are valid.
- The zone holds fewer than 200 records besides SOA and NS.
- 409
dns_conflict: a CNAME and another record share a name. This happens most often at the DKIM name. Delete the record that is in the way. Use the record type your provider names. - 503
upstream_failed: the API could not look up the MX host at that moment. Nothing was saved. Try again a little later. - 403
forbidden: the key lacks a scope or is limited to other DNS zones or records. When a key is refused