Domains · MachineMail Docs

Adding a domain

Inboxes on machinemail.dev work out of the box. Add a custom domain when you want addresses like support@agents.acme.dev, catch-all addresses, or sending that is authenticated as your own domain. Subdomains work the same as apex domains, and keep your main domain’s mail setup untouched.

For a step-by-step walkthrough, see Use a custom domain.

Adding a domain

Terminal

curl https://api.machinemail.dev/v0/domains \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain": "agents.acme.dev", "catch_all": true, "catch_all_ttl_seconds": 86400}'
Field Notes
domain Required. A hostname. Must not already be registered by any organization.
inbound_enabled Default true. When false, the MX record is not required and the domain is send-only.
catch_all Default false. Accept mail to any address. See Catch-all.
catch_all_ttl_seconds Make catch-all inboxes ephemeral with this TTL.
feedback_enabled Default false. Stored for AgentMail compatibility.
client_id Idempotency key.

The response lists the DNS records to publish:

Response

{
  "domain_id": "dom_01m4fy8hmc0000fpjt8gvxtayy",
  "organization_id": "org_01m4fy8h140000efvagszh6rpx",
  "pod_id": "pod_01m4fy8h150000w4xty1ehfnqa",
  "domain": "agents.acme.dev",
  "status": "NOT_STARTED",
  "reason": "Publish the DNS records, then call POST /v0/domains/{domain_id}/verify",
  "feedback_enabled": false,
  "inbound_enabled": true,
  "catch_all": true,
  "catch_all_ttl_seconds": 86400,
  "nameservers": ["ns23.domaincontrol.com", "ns24.domaincontrol.com"],
  "dns_provider": "godaddy",
  "dns_provider_url": "https://dcc.godaddy.com/control/acme.dev/dns",
  "subdomains_enabled": false,
  "tracking_enabled": false,
  "records": [
    { "type": "TXT", "name": "_dmarc.agents.acme.dev", "value": "v=DMARC1; p=none", "status": "MISSING", "purpose": "dmarc", "required": false },
    { "type": "MX", "name": "agents.acme.dev", "value": "inbound-smtp.us-east-1.amazonaws.com", "priority": 10, "status": "MISSING", "purpose": "inbound", "required": true },
    { "type": "CNAME", "name": "4f3k2j..._domainkey.agents.acme.dev", "value": "4f3k2j....dkim.amazonses.com", "status": "MISSING", "purpose": "dkim", "required": true },
    { "type": "MX", "name": "mf.agents.acme.dev", "value": "feedback-smtp.us-east-1.amazonses.com", "priority": 10, "status": "MISSING", "purpose": "provider", "required": true },
    { "type": "TXT", "name": "mf.agents.acme.dev", "value": "v=spf1 include:amazonses.com ~all", "status": "MISSING", "purpose": "spf", "required": true }
  ],
  "created_at": "2026-10-09T09:00:16.207Z",
  "updated_at": "2026-10-09T09:00:16.207Z"
}

Always publish the records exactly as the API returns them for your domain. The set can differ between domains, for example when sending records are issued per domain, in which case purpose is provider.

DNS records

purpose What it does Required
inbound MX record that routes mail for the domain to MachineMail. Yes, when inbound_enabled is true.
spf Authorises Amazon SES to send for the domain, on the sending subdomain mf.<domain>. Yes
dkim The key receivers use to check the signature on your mail: three CNAMEs to Amazon SES’s keys. Yes
provider The MX of the sending subdomain mf.<domain>, where bounces and complaints come back. Yes
dmarc A starter DMARC policy. No. Skip it if you already have DMARC.

Each record has a status:

Record status Meaning
MISSING Nothing found at that name.
INVALID Something is there but it does not match. reason shows what was found.
VALID Matches.

An existing SPF record is fine as long as it contains the include: from the API’s record. Any DMARC record counts as valid.

Verification

Terminal

curl -X POST https://api.machinemail.dev/v0/domains/dom_01m4fy8hmc0000fpjt8gvxtayy/verify \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"

verify looks up every record now and returns the updated domain.

Domain status Meaning
NOT_STARTED Added but never verified.
PENDING Some required records are still missing.
INVALID A required record exists with the wrong value.
VERIFYING Every required record is valid; Amazon SES has not confirmed them yet.
FAILED Amazon SES gave up on the records: DKIM was not found within 72 hours.
VERIFIED Every required record is valid. verified_at is set and a domain.verified event fires.

reason explains what is outstanding, for example "MX agents.acme.dev is missing". It is absent once the domain is verified.

After the first verify, MachineMail keeps re-checking PENDING, INVALID and VERIFYING domains in the background about every 5 minutes, for up to 7 days after the domain was added. A domain in NOT_STARTED is not checked automatically, so call verify once after publishing the records. last_checked_at shows when the last check ran.

A domain that is never verified is removed 14 days after it was added, together with its inboxes and the mail in them, so that nobody can hold on to a domain they do not own. The owners and admins of the organization get an email at least 3 days before, with the date. A domain that was verified once is never removed this way.

Note

You can create inboxes on a domain right after adding it. Mail only arrives once the MX record resolves, and catch-all only works once the domain is VERIFIED. Wait for verification before sending from the domain; until SPF and DKIM are in place, receivers are likely to reject or junk your mail.

DNS host detection

When you add or verify a domain, MachineMail looks up the zone’s nameservers and recognises common DNS hosts. The domain then carries:

The dashboard uses these to show an Open in [provider] button on the domain page.

Domain Connect

Domain Connect lets a DNS host add records for you after you approve them in its own UI. Cloudflare, GoDaddy, IONOS and others support it. When it works for your zone, the dashboard shows Add records at [provider], which opens the provider with MachineMail’s records prefilled.

Terminal

curl https://api.machinemail.dev/v0/domains/dom_01m4fy8hmc0000fpjt8gvxtayy/domain-connect \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"

Response

{
  "supported": false,
  "provider_id": "cloudflare.com",
  "provider_name": "Cloudflare",
  "url_sync_ux": "https://dash.cloudflare.com/domainconnect",
  "reason": "template_not_supported",
  "checked_at": "2026-10-09T09:05:12.000Z"
}

When supported is true, apply_url is a signed link that adds only the records still outstanding. Open it in a browser (a popup of width by height, or a new tab). After you approve, the provider sends you back to the domain page in the dashboard, which runs a verify. The domain object also carries a short domain_connect summary (supported, provider_name, apply_url) from the last check.

reason explains why there is no apply URL:

reason Meaning
no_domain_connect The zone’s DNS host does not support Domain Connect.
no_sync_flow The host supports Domain Connect but not the synchronous flow MachineMail uses.
template_not_supported The host has not onboarded MachineMail’s template yet.
records_not_templatable The domain’s records don’t fit the template.
already_configured Every record is already in place.
provider_error The host’s Domain Connect API failed. Retried after about 10 minutes.
not_configured Domain Connect is not available yet.

Warning

Domain Connect onboarding with DNS providers is still pending. Until MachineMail’s template is published and accepted by each provider, discovery returns template_not_supported (or records_not_templatable when the records don’t fit the template), and the dashboard falls back to the Open in [provider] console link and the zone file. Add the records by hand in the meantime.

Zone file

GET /v0/domains/{domain_id}/zone-file returns the records as a BIND zone file (text/plain, downloaded as agents.acme.dev.zone). Many DNS hosts, including Cloudflare, can import it.

Terminal

curl https://api.machinemail.dev/v0/domains/dom_01m4fy8hmc0000fpjt8gvxtayy/zone-file \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" -o agents.acme.dev.zone

Catch-all

With catch_all: true on a verified domain with inbound enabled, mail to any address at the domain is accepted. The first message to a new address creates the inbox with created_by: "catch_all" in the domain’s pod. With catch_all_ttl_seconds set, those inboxes are ephemeral and are deleted after that long without new mail.

The local part must be a valid username (a-z, 0-9 and . _ + -). Mail to invalid local parts is not delivered.

Catch-all is off by default and never applies to the shared machinemail.dev domain.

Updating and deleting

PATCH /v0/domains/{domain_id} accepts catch_all, catch_all_ttl_seconds (null to clear), inbound_enabled and feedback_enabled. Turning inbound_enabled off makes the MX record optional and recomputes the status.

DELETE /v0/domains/{domain_id} removes the domain from MachineMail. Inboxes already created on it are not deleted; delete them separately. Inbox-scoped keys cannot add domains; pod keys see and add domains in their pod only.