Use a custom domain
Add your own domain to MachineMail, publish the MX, SPF, DKIM and DMARC records (by hand, from a zone file or with Domain Connect), verify it, and turn on catch-all addresses.
Inboxes on the shared machinemail.dev domain work with no setup. Add your own domain when you want agents to send from your brand, want unlimited addresses, or want catch-all addresses that exist without an API call.
The flow is: add the domain, publish its DNS records, verify, then create inboxes on it.
Add the domain
Field Default Meaning domainrequired The hostname. A subdomain such as agents.acme.devkeeps agent mail apart from your people’s mail.inbound_enabledtrueReceive mail at this domain. When false, the MX record is not required and the domain is send-only.catch_allfalseAccept mail for any address at the domain, creating the inbox on first delivery. catch_all_ttl_secondsnone Make catch-all inboxes ephemeral with this TTL. feedback_enabledfalseAgentMail field, stored and returned. client_idnone Idempotency key. A repeat with the same value returns the existing domain. You can also add a domain in the dashboard at
/app/domains. The key needsdomain_create. Each domain can belong to one organization only; adding one that is already registered returns409 conflict.Read the DNS records
The response lists the records to publish. The values below are typical; always publish exactly what the API returns for your domain, because the DKIM key is unique per domain and the set can differ.
Purpose Record Why inboundMX to mx.machinemail.dev, priority 10Routes mail for the domain to MachineMail. Required only when inbound_enabledis true.spfTXT v=spf1 include:spf.machinemail.dev ~allAuthorizes MachineMail to send for the domain. If you already have an SPF record, add the include:to it instead of publishing a second one.dkimTXT at machinemail._domainkey.<domain>Public key for the signature on your outgoing mail. dmarcTXT at _dmarc.<domain>A starting DMARC policy. Optional: any existing DMARC record counts. providervaries Extra records some domains need for sending. Publish them when present. required: truerecords must all be valid before the domain verifies.Publish the records
MachineMail looks up the domain’s nameservers when you add it and reports the DNS host in
dns_provider(cloudflare,route53,google,vercel,namecheap,godaddy,digitalocean,hetzner,porkbunorother), with a deep link to that host’s DNS console indns_provider_urlwhen one is known.You have three ways to get the records into your zone.
By hand. Open
dns_provider_url(the dashboard’s Open in Provider button) and create each record.Zone file. Download a BIND zone file with the records and import it, if your DNS host supports imports (Cloudflare does):
The dashboard has the same download as the Zone file button on
/app/domains/<domain_id>.Domain Connect (one click). Some DNS hosts, Cloudflare included, support Domain Connect. When yours does, the dashboard shows Add records at Provider. It opens your DNS host’s own approval screen with the MachineMail records prefilled. You approve, the host adds them, and you land back on the domain page, which runs a verification.
Warning
Domain Connect is not available yet. Each DNS host has to accept the MachineMail template before the one-click flow works, and that onboarding, including with Cloudflare, is still in progress. Until then expect
supported: falsewithreason: "template_not_supported", and use the console link or the zone file.To check Domain Connect from the API:
When
supportedis true the response also hasapply_url, a signed link to your DNS host. Open it in a browser (as a popup ofwidthbyheightpixels when those are set). It only adds the records that are still missing: the MX if inbound is enabled, SPF and DKIM, and DMARC only when the domain has no DMARC record at all. Whensupportedis false,reasonsays why:reasonMeaning no_domain_connectYour DNS host does not support Domain Connect. template_not_supportedYour DNS host supports it but has not accepted the MachineMail template yet. no_sync_flowYour DNS host only offers a flow MachineMail does not use. records_not_templatableThis domain’s records do not fit the template, for example because it needs providerrecords.already_configuredNothing is left to add ( supportedis true, there is noapply_url).provider_errorThe DNS host did not answer. Retried after 10 minutes. not_configuredDomain Connect is turned off. Every domain object also carries a short
domain_connectsummary (supported,provider_name,apply_url) from the last check. Discovery results are cached for 6 hours and refreshed on every verify.Verify
DNS changes can take minutes to propagate. Then run the checks:
The response is the domain with fresh per-record
status(MISSING,INVALIDorVALID) and an overallstatus:Domain statusMeaning NOT_STARTEDNever verified. PENDINGSome required records are still missing. INVALIDA required record exists with the wrong value. VERIFIEDEvery required record is valid. verified_atis set.Call verify at least once. After that, MachineMail re-checks
PENDINGandINVALIDdomains in the background about every 5 minutes for 7 days after the domain was added, so a slow DNS change is picked up without polling. A domain inNOT_STARTEDis not re-checked on its own.When the domain becomes verified, MachineMail emits
domain.verifiedwith the domain indata.domain. Subscribe a webhook to it if you automate onboarding. Verify needs thedomain_updatepermission.Create inboxes on the domain
Pass
domainwhen you create an inbox:Inboxes on your own domains do not count toward the per-organization cap on the shared domain.
Note
The API lets you create inboxes on a domain before it verifies, but mail will not arrive until the MX record is live, and receivers are likely to reject or junk what you send until SPF and DKIM pass. Verify first.
Catch-all addresses
With catch_all: true, mail to any address at the domain is accepted, and the inbox is created on first delivery. That needs all of:
- the domain is
VERIFIED, inbound_enabledis true,- the local part is a valid username: lowercase letters, digits and
. _ + -, 1 to 64 characters, starting and ending with a letter or digit.
Catch-all inboxes have created_by: "catch_all" and emit inbox.created like any other. If the domain has catch_all_ttl_seconds, they are ephemeral: they expire that long after the last message arrives and are then deleted with their mail. Inbox IDs are lowercased, so Signup+GitHub@agents.acme.dev lands in signup+github@agents.acme.dev.
Turn catch-all on or off later with PATCH /v0/domains/{domain_id}, which also accepts catch_all_ttl_seconds (null to clear it), inbound_enabled and feedback_enabled.
Warning
A catch-all domain accepts mail for every address, including guesses and spam traps. Every message still gets a trust report, but use a dedicated subdomain for catch-all rather than your main domain.
Troubleshooting
When verification fails, the domain’s reason lists the problem records, and each INVALID record has its own reason showing what DNS returned:
| Symptom | Fix |
|---|---|
SPF INVALID | You have an SPF record without the MachineMail include. Add include:spf.machinemail.dev to it. Do not publish two SPF records. |
MX INVALID | Another MX record answers for the name. For a subdomain like agents.acme.dev, put the MX on the subdomain, not on acme.dev. |
DKIM MISSING | Some DNS consoles append the zone to the name. Enter machinemail._domainkey.agents in a zone for acme.dev, not the full name. |
DKIM INVALID | The key was cut or wrapped. Long TXT values are split into quoted chunks in the zone file; paste the value from the API as one string. |
Everything MISSING | DNS has not propagated yet, or the records are in a different zone than the nameservers in nameservers. |
DELETE /v0/domains/{domain_id} removes the domain. Remove the DNS records afterwards.