Mailfully logo

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

1
Create the domain
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.

2
Publish the DNS records

Add each record at your DNS host exactly as returned:

RecordsPurpose
3 CNAMEs at <token>._domainkey.mail.acme.comDKIM signing
MX at send.mail.acme.com, priority 10Custom MAIL FROM
TXT at send.mail.acme.comSPF for the MAIL FROM subdomain
TXT at _dmarc.mail.acme.comDMARC 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}.

3
Verify

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:

statusMeaning
pendingWithin the verification window; records not yet confirmed
partially_verifiedExactly one of DKIM and MAIL FROM has verified
verifiedDKIM and MAIL FROM both verified. The domain can send live email
failedThe 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.