Verify a domain
Add a sending domain, publish its DNS records, and verify it before sending live email.
Add the domain, publish the records it returns, then check until status is verified. Live sends are refused until you have a verified domain; see Live sending is refused. Test-mode sends are exempt, so you can build your integration before DNS is set up.
Add and verify
curl -X POST https://api.mailfully.com/v1/domains \
-H "Authorization: Bearer mf_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "name": "mail.acme.com" }'The 201 response includes the domain and the exact DNS records to publish:
{
"object": "domain",
"id": "dom_01J1PZ3M8Q7VXCK4T2R9WFH6BD",
"name": "mail.acme.com",
"region": "us-east-1",
"mail_from_subdomain": "send.mail.acme.com",
"dkim_tokens": [
"6gbrjpgwjskckoa6a5zn6fwqkn67xbtw",
"yybhvng4tqglsubhkkbwfwbtvvxjb5lb",
"wrx2s7fgbfyvfh2c3wch5bqk3gpjrmtp"
],
"dkim_status": "not_started",
"mail_from_status": "not_started",
"dmarc_status": "not_started",
"tracking_status": "not_started",
"tracking_enabled": false,
"config_set": "mf-org_01J1PZ0R2NQ8XWJH3KTV6C5FYD-dom_01J1PZ3M8Q7VXCK4T2R9WFH6BD",
"status": "pending",
"created_at": "2026-07-08T14:05:02.318Z",
"receiving": false,
"records": [
{ "type": "CNAME", "name": "6gbrjpgwjskckoa6a5zn6fwqkn67xbtw._domainkey.mail.acme.com", "host": "6gbrjpgwjskckoa6a5zn6fwqkn67xbtw._domainkey.mail.acme.com", "value": "6gbrjpgwjskckoa6a5zn6fwqkn67xbtw.dkim.mailfully.com" },
{ "type": "CNAME", "name": "yybhvng4tqglsubhkkbwfwbtvvxjb5lb._domainkey.mail.acme.com", "host": "yybhvng4tqglsubhkkbwfwbtvvxjb5lb._domainkey.mail.acme.com", "value": "yybhvng4tqglsubhkkbwfwbtvvxjb5lb.dkim.mailfully.com" },
{ "type": "CNAME", "name": "wrx2s7fgbfyvfh2c3wch5bqk3gpjrmtp._domainkey.mail.acme.com", "host": "wrx2s7fgbfyvfh2c3wch5bqk3gpjrmtp._domainkey.mail.acme.com", "value": "wrx2s7fgbfyvfh2c3wch5bqk3gpjrmtp.dkim.mailfully.com" },
{ "type": "MX", "name": "send.mail.acme.com", "host": "send.mail.acme.com", "value": "feedback-smtp.us-east-1.amazonses.com", "priority": 10 },
{ "type": "TXT", "name": "send.mail.acme.com", "host": "send.mail.acme.com", "value": "v=spf1 include:_spf.mailfully.com ~all" },
{ "type": "TXT", "name": "_dmarc.mail.acme.com", "host": "_dmarc.mail.acme.com", "value": "v=DMARC1; p=none;" }
]
}The optional mail_from_prefix field controls the MAIL FROM subdomain; it defaults to send, so the MX and SPF records land on send.mail.acme.com.
A domain nobody can own, such as gmail.com, is refused. See Adding a domain was refused.
Add each record at your DNS host exactly as returned:
| Records | Purpose |
|---|---|
3 CNAMEs at <token>._domainkey.mail.acme.com | DKIM signing |
MX at send.mail.acme.com, priority 10 | Custom MAIL FROM |
TXT at send.mail.acme.com | SPF for the MAIL FROM subdomain |
TXT at _dmarc.mail.acme.com | DMARC policy, starting at p=none (monitor-only) |
Each record has a host. For a root domain such as acme.com it is the name relative to the domain, and the domain page can show either form (Host / Full name). For a subdomain such as mail.acme.com it is the full name; most DNS hosts want the part before the zone you manage (see DNS records).
If your DNS is at Cloudflare, the domain page shows Set up with Cloudflare: sign in and approve the records.
If you lose the response, fetch the same records any time with GET /v1/domains/{id}.
Each call to the verify endpoint checks your DNS again and returns the updated domain:
curl -X POST https://api.mailfully.com/v1/domains/dom_01J1PZ3M8Q7VXCK4T2R9WFH6BD/verify \
-H "Authorization: Bearer mf_live_xxxxxxxxxxxx"A 200 response mid-verification, with MAIL FROM confirmed and DKIM still propagating:
{
"object": "domain",
"id": "dom_01J1PZ3M8Q7VXCK4T2R9WFH6BD",
"name": "mail.acme.com",
"region": "us-east-1",
"mail_from_subdomain": "send.mail.acme.com",
"dkim_tokens": [
"6gbrjpgwjskckoa6a5zn6fwqkn67xbtw",
"yybhvng4tqglsubhkkbwfwbtvvxjb5lb",
"wrx2s7fgbfyvfh2c3wch5bqk3gpjrmtp"
],
"dkim_status": "pending",
"mail_from_status": "verified",
"dmarc_status": "not_started",
"tracking_status": "not_started",
"tracking_enabled": false,
"config_set": "mf-org_01J1PZ0R2NQ8XWJH3KTV6C5FYD-dom_01J1PZ3M8Q7VXCK4T2R9WFH6BD",
"status": "partially_verified",
"created_at": "2026-07-08T14:05:02.318Z"
}Repeat the call until status is verified. Live sends from orders@mail.acme.com are accepted from that point on. The domain in the from address must itself be verified: a subdomain of a verified domain does not inherit it.
Checking stops 72 hours after you add the domain. To try again, fix the records, then click Refresh status on the domain page, or call POST /v1/domains/{id}/verify with {"restart_if_failed": true}. That starts a new 72-hour window.
curl -X POST https://api.mailfully.com/v1/domains/dom_01J1PZ3M8Q7VXCK4T2R9WFH6BD/verify \
-H "Authorization: Bearer mf_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "restart_if_failed": true }'Verification statuses
The domain's status moves through four values:
status | Meaning |
|---|---|
pending | Within the verification window; records not yet confirmed |
partially_verified | Exactly one of DKIM and MAIL FROM has verified |
verified | DKIM and MAIL FROM both verified. The domain can send live email |
failed | The 72-hour window elapsed with neither DKIM nor MAIL FROM verified. If exactly one made it, the domain finalizes as partially_verified instead |
Per-record fields (dkim_status, mail_from_status) move through not_started, pending, verified, and failed. Verification gates on DKIM and MAIL FROM only. The DMARC record is advisory and never blocks the verified status.
Any record still unverified when the 72-hour window ends is marked failed.
Open and click tracking
Tracking is a single boolean per domain that covers opens and clicks together. Turning it on starts recording open and click events for mail sent from the domain. It needs no DNS record:
curl -X POST https://api.mailfully.com/v1/domains/dom_01J1PZ3M8Q7VXCK4T2R9WFH6BD/tracking \
-H "Authorization: Bearer mf_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "enabled": true }'The response returns the updated domain. Send { "enabled": false } to turn tracking off. See POST /v1/domains/{id}/tracking for the full response shape.
Troubleshooting
DNS propagation can take minutes to hours, depending on your host and record TTLs. A record that fails a check inside the 72-hour window is not final. Keep the records published and verify again. See Domain will not verify for how to read the per-record statuses while you wait.
The three DKIM records must be plain CNAMEs that resolve to their dkim.mailfully.com targets. If your DNS host proxies or flattens CNAME records, the published name may stop resolving to that target, so publish them as unproxied, DNS-only records. Also check for hosts that auto-append the zone name: if yours does, enter only the host part (<token>._domainkey) so the record doesn't end up at <token>._domainkey.mail.acme.com.mail.acme.com. Confirm with dig CNAME <token>._domainkey.mail.acme.com +short. See Publishing your DNS records for the naming traps in more detail, and its provider guides for your DNS host.
Mailfully's SPF TXT record lives on the MAIL FROM subdomain (send.mail.acme.com), not your root domain, so it does not conflict with an existing SPF record on your apex. Publish it as returned. If you already publish an SPF TXT on that exact subdomain, merge include:_spf.mailfully.com into the existing record instead (a single name must not carry two SPF records). See MX record conflicts for how this and the MAIL FROM MX record relate to records you already publish.
Related
POST /v1/domains: full request and response referencePOST /v1/domains/{id}/verify- Errors: the full error catalog
