domains.md


title: "Domains" description: "Add custom domains to MachineMail, get their DNS records, zone file and Domain Connect setup, verify them and turn on catch-all, for the organization or a pod." canonical: "https://machinemail.dev/docs/reference/api/domains"

Domains

Add custom domains to MachineMail, get their DNS records, zone file and Domain Connect setup, verify them and turn on catch-all, for the organization or a pod.

Source: https://machinemail.dev/docs/reference/api/domains

Method Path Summary
GET /v0/domains List Domains
POST /v0/domains Create Domain
GET /v0/domains/{domain_id} Get Domain
PATCH /v0/domains/{domain_id} Update Domain
DELETE /v0/domains/{domain_id} Delete Domain
GET /v0/domains/{domain_id}/domain-connect Get Domain Connect Setup
POST /v0/domains/{domain_id}/verify Verify Domain
GET /v0/domains/{domain_id}/zone-file Get Zone File
GET /v0/pods/{pod_id}/domains List Domains in Pod
POST /v0/pods/{pod_id}/domains Create Domain in Pod
GET /v0/pods/{pod_id}/domains/{domain_id} Get Domain in Pod
PATCH /v0/pods/{pod_id}/domains/{domain_id} Update Domain in Pod
DELETE /v0/pods/{pod_id}/domains/{domain_id} Delete Domain in Pod
GET /v0/pods/{pod_id}/domains/{domain_id}/domain-connect Get Domain Connect Setup in Pod
POST /v0/pods/{pod_id}/domains/{domain_id}/verify Verify Domain in Pod
GET /v0/pods/{pod_id}/domains/{domain_id}/zone-file Get Zone File in Pod

List Domains

GET /v0/domains

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_read.

Query parameters

Field Type Description
limit integer Maximum number of items to return. Default 50, maximum 500; larger values are capped.
page_token string next_page_token from the previous page.
ascending boolean Sort oldest first.

Response

200 A page of domains (application/json)

Field Type Description
count integer required Number of items returned.
limit integer Maximum number of items per page. Default 50, maximum 500; larger values are capped.
next_page_token string Page token for pagination. Absent on the last page.
domains DomainItem[] required Ordered by created_at, newest first unless ascending is set.

Each item of domains is a DomainItem:

Field Type Description
pod_id string required ID of the pod the domain belongs to.
domain_id string required ID of the domain.
domain string required The domain name (for example example.com), lowercased.
feedback_enabled boolean required Stored as given. MachineMail does not send bounce and complaint notifications to inboxes; use the message.bounced and message.complained events.
inbound_enabled boolean required Receive email for this domain. false makes it send-only: the inbound MX record is not required.
subdomains_enabled boolean required Always false: inboxes on subdomains are not supported.
tracking_enabled boolean required Always false: open tracking is not supported.
client_id string Client ID given at creation (idempotency key).
updated_at string (date-time) required Time at which the domain was last updated.
created_at string (date-time) required Time at which the domain was created.
status "NOT_STARTED" | "PENDING" | "INVALID" | "FAILED" | "VERIFYING" | "VERIFIED" required Verification status, as on Domain.
reason string Why the domain is not verified yet, as on Domain.
records VerificationRecord[] required DNS records to publish, as on Domain.
organization_id string required ID of the organization.
catch_all boolean required Mail to any address at the domain without an inbox creates one (created_by: catch_all).
catch_all_ttl_seconds integer TTL of inboxes created by catch-all; absent means they are permanent.
nameservers string[] Authoritative nameservers, detected on create and verify.
dns_provider "cloudflare" | "route53" | "google" | "vercel" | "namecheap" | "godaddy" | "digitalocean" | "hetzner" | "porkbun" | "other" DNS host the nameservers belong to.
dns_provider_url string Link to the DNS host console.
domain_connect object One-click DNS setup (Domain Connect), from the last discovery. See Get Domain Connect Setup.
verified_at string (date-time) When the domain became VERIFIED.
last_checked_at string (date-time) When the DNS records were last checked.

Errors

Status Meaning
400 Invalid request: error.type is validation and error.param names the field.
401 Missing, invalid, expired or revoked credential.
403 The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
500 Unexpected server error.

Error bodies use the error envelope.

Example

curl "https://api.machinemail.dev/v0/domains" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "domains": [
    {
      "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
      "domain_id": "dom_01k6x3d5kq0000g3q9sx5b8j0m",
      "domain": "example.com",
      "feedback_enabled": false,
      "inbound_enabled": false,
      "subdomains_enabled": false,
      "tracking_enabled": false,
      "client_id": "my-client-id",
      "updated_at": "2026-10-01T12:00:00.000Z",
      "created_at": "2026-10-01T12:00:00.000Z",
      "status": "NOT_STARTED",
      "reason": "string",
      "records": [
        {
          "type": "TXT",
          "name": "Production",
          "value": "string",
          "status": "MISSING",
          "priority": 1,
          "reason": "string",
          "purpose": "inbound",
          "required": false
        }
      ],
      "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
      "catch_all": false,
      "catch_all_ttl_seconds": 1,
      "nameservers": ["string"],
      "dns_provider": "cloudflare",
      "dns_provider_url": "https://example.com",
      "domain_connect": {
        "supported": false,
        "provider_name": "string",
        "apply_url": "https://example.com"
      },
      "verified_at": "2026-10-01T12:00:00.000Z",
      "last_checked_at": "2026-10-01T12:00:00.000Z"
    }
  ]
}

Create Domain

POST /v0/domains

Registers the domain and returns the DNS records to publish (status: NOT_STARTED). Publish them, then call Verify Domain. A background worker re-checks PENDING, INVALID and VERIFYING domains about every 5 minutes until 7 days after creation; a NOT_STARTED domain is only checked by Verify Domain. A domain that is never verified is removed 14 days after it was added, together with its inboxes and the mail in them; owners and admins of the organization are emailed at least 3 days before. A domain that was verified once is never removed this way.

A domain can be registered by one organization only (409), and the shared domain cannot be added (409). Inbox-scoped credentials cannot add domains (403).

allow_conflicting_provider, subdomains_enabled and tracking_enabled are not supported.

502 upstream_error when the email provider cannot register the domain (for example its domain capacity is reached, or it is rate limiting registrations): nothing is created.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_create.

Request body

Content type: application/json.

Field Type Description
domain string required The domain name, for example example.com. Cannot be the shared domain or a domain another organization registered.
feedback_enabled boolean Stored and returned; has no effect. Default false.
inbound_enabled boolean Receive email for this domain. Default true. false makes it send-only (the inbound MX record is not required).
client_id string Idempotency key: a create with a client_id already used in the organization returns that domain with 200, or 409 when that domain is outside the pod of the API key or route.
pod_id string Pod to create the domain in. Defaults to the credential pod or the organization default pod. A pod-scoped credential can only name its own pod.
catch_all boolean Create an inbox for any address at the domain that receives mail. Default false.
catch_all_ttl_seconds integer | null Make inboxes created by catch-all ephemeral with this TTL. null or absent: permanent.

Response

200 A domain with this client_id already exists; it is returned unchanged. (application/json)

Each item of records is a VerificationRecord:

Field Type Description
type "TXT" | "CNAME" | "MX" required
name string required Name (host) of the record.
value string required Value of the record.
status "MISSING" | "INVALID" | "VALID" required MISSING: not published yet. INVALID: published with the wrong value.
priority integer Priority of an MX record.
reason string Why an INVALID record is invalid: Found: .
purpose "inbound" | "spf" | "dkim" | "dmarc" | "provider" required What the record is for. provider records come from the sending provider.
required boolean required Whether the record must be valid for the domain to verify. The inbound MX record is not required when inbound_enabled is false.

201 Created (application/json)

Each item of records is a VerificationRecord:

Errors

Status Meaning
400 Invalid request: error.type is validation and error.param names the field.
401 Missing, invalid, expired or revoked credential.
402 Free plan: the organization already has its 1 custom domain (limit_key: "custom_domains"). Paid plans can add domains past their allowance (billed as extra).
403 The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
409 Conflicts with an existing resource.
500 Unexpected server error.
502 The email provider refused or failed the send (error.type is upstream_error). Nothing was sent and no message was stored. error.message says why: a rejected message or recipient is final, and so is a used-up sending quota until it resets; a throttled or unavailable provider can be retried later. A scheduled draft whose send fails this way is marked send_status: failed, unless the provider could not take mail for now (throttled, sending paused, quota used up, or unavailable): it stays scheduled and is retried.

Error bodies use the error envelope.

Example

curl -X POST "https://api.machinemail.dev/v0/domains" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "domain": "example.com",
  "feedback_enabled": false
}'
{
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "domain_id": "dom_01k6x3d5kq0000g3q9sx5b8j0m",
  "domain": "example.com",
  "feedback_enabled": false,
  "inbound_enabled": false,
  "subdomains_enabled": false,
  "tracking_enabled": false,
  "client_id": "my-client-id",
  "updated_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "status": "NOT_STARTED",
  "reason": "string",
  "records": [
    {
      "type": "TXT",
      "name": "Production",
      "value": "string",
      "status": "MISSING",
      "priority": 1,
      "reason": "string",
      "purpose": "inbound",
      "required": false
    }
  ],
  "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
  "catch_all": false,
  "catch_all_ttl_seconds": 1,
  "nameservers": ["string"],
  "dns_provider": "cloudflare",
  "dns_provider_url": "https://example.com",
  "domain_connect": {
    "supported": false,
    "provider_name": "string",
    "apply_url": "https://example.com"
  },
  "verified_at": "2026-10-01T12:00:00.000Z",
  "last_checked_at": "2026-10-01T12:00:00.000Z"
}

Get Domain

GET /v0/domains/{domain_id}

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_read.

Path parameters

Field Type Description
domain_id string required ID of the domain.

Response

200 The domain (application/json)

Each item of records is a VerificationRecord:

Errors

Status Meaning
401 Missing, invalid, expired or revoked credential.
403 The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404 Not found. Also returned for resources outside the credential scope.
500 Unexpected server error.

Error bodies use the error envelope.

Example

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

Update Domain

PATCH /v0/domains/{domain_id}

Fields you omit are unchanged. subdomains_enabled and tracking_enabled are not supported.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_update.

Path parameters

Field Type Description
domain_id string required ID of the domain.

Request body

Content type: application/json.

Field Type Description
feedback_enabled boolean Stored and returned; has no effect.
inbound_enabled boolean Receive email for this domain. false makes the inbound MX record optional and re-evaluates the status.
catch_all boolean Create an inbox for any address at the domain that receives mail.
catch_all_ttl_seconds integer | null TTL of inboxes created by catch-all from now on. null makes them permanent.

Response

200 The updated domain (application/json)

Each item of records is a VerificationRecord:

Errors

Error bodies use the error envelope.

Example

curl -X PATCH "https://api.machinemail.dev/v0/domains/dom_01k6x3d5kq0000g3q9sx5b8j0m" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "feedback_enabled": false
}'

Delete Domain

DELETE /v0/domains/{domain_id}

Inboxes on the domain are not deleted.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_delete.

Path parameters

Field Type Description
domain_id string required ID of the domain.

Response

200 Deleted (application/json)

Field Type Description
ok true required

Errors

Error bodies use the error envelope.

Example

curl -X DELETE "https://api.machinemail.dev/v0/domains/dom_01k6x3d5kq0000g3q9sx5b8j0m" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
{
  "ok": true
}

Get Domain Connect Setup

GET /v0/domains/{domain_id}/domain-connect

Discovers whether the domain's DNS provider supports Domain Connect (TXT _domainconnect.<zone> and the provider's /v2/<zone>/settings) and has onboarded the MachineMail template (machinemail.dev/email). When it has, apply_url is a signed synchronous-flow link (RS256, key=_dck1, sig last) that opens the provider with the outstanding MX, SPF, DKIM and DMARC records prefilled. Discovery is cached on the domain (6 h; provider errors 10 min) and re-run by Verify Domain.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_read.

Path parameters

Field Type Description
domain_id string required ID of the domain.

Response

200 Domain Connect support for the domain (application/json)

Field Type Description
supported boolean required The zone DNS provider supports Domain Connect and has the MachineMail template.
provider_name string Display name of the DNS provider.
apply_url string Signed link that opens the provider with the outstanding records prefilled. Absent when nothing is left to add.
provider_id string Domain Connect provider ID.
url_sync_ux string Base URL of the provider synchronous flow.
width integer Suggested popup width, in pixels.
height integer Suggested popup height, in pixels.
reason "no_domain_connect" | "provider_error" | "no_sync_flow" | "template_not_supported" | "records_not_templatable" | "already_configured" | "not_configured" Why one-click setup is not available, or already_configured when nothing is left to add.
checked_at string (date-time) required When discovery last ran.

Errors

Error bodies use the error envelope.

Example

curl "https://api.machinemail.dev/v0/domains/dom_01k6x3d5kq0000g3q9sx5b8j0m/domain-connect" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
{
  "supported": false,
  "provider_name": "string",
  "apply_url": "https://example.com",
  "provider_id": "string",
  "url_sync_ux": "string",
  "width": 1,
  "height": 1,
  "reason": "no_domain_connect",
  "checked_at": "2026-10-01T12:00:00.000Z"
}

Verify Domain

POST /v0/domains/{domain_id}/verify

Checks every DNS record now, updates records[].status and status, and re-runs Domain Connect discovery. A domain.verified event is emitted when the domain becomes VERIFIED.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_update.

Path parameters

Field Type Description
domain_id string required ID of the domain.

Response

200 The domain after the check (application/json)

Each item of records is a VerificationRecord:

Errors

Error bodies use the error envelope.

Example

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

Get Zone File

GET /v0/domains/{domain_id}/zone-file

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_read.

Path parameters

Field Type Description
domain_id string required ID of the domain.

Response

200 A BIND zone file with the records to publish (text/plain)

Headers:

Field Type Description
Content-Disposition string attachment; filename=".zone"

Errors

Error bodies use the error envelope.

Example

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

List Domains in Pod

GET /v0/pods/{pod_id}/domains

Same as List Domains (GET /v0/domains), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_read.

Path parameters

Field Type Description
pod_id string required ID of the pod.

Query parameters

Response

200 A page of domains (application/json)

Each item of domains is a DomainItem:

Errors

Error bodies use the error envelope.

Example

curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/domains" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"

Create Domain in Pod

POST /v0/pods/{pod_id}/domains

Same as Create Domain (POST /v0/domains), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_create.

Path parameters

Field Type Description
pod_id string required ID of the pod.

Request body

Content type: application/json.

Response

200 A domain with this client_id already exists; it is returned unchanged. (application/json)

Each item of records is a VerificationRecord:

201 Created (application/json)

Each item of records is a VerificationRecord:

Errors

Error bodies use the error envelope.

Example

curl -X POST "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/domains" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "domain": "example.com",
  "feedback_enabled": false
}'

Get Domain in Pod

GET /v0/pods/{pod_id}/domains/{domain_id}

Same as Get Domain (GET /v0/domains/{domain_id}), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_read.

Path parameters

Field Type Description
pod_id string required ID of the pod.
domain_id string required ID of the domain.

Response

200 The domain (application/json)

Each item of records is a VerificationRecord:

Errors

Error bodies use the error envelope.

Example

curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/domains/dom_01k6x3d5kq0000g3q9sx5b8j0m" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"

Update Domain in Pod

PATCH /v0/pods/{pod_id}/domains/{domain_id}

Same as Update Domain (PATCH /v0/domains/{domain_id}), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_update.

Path parameters

Request body

Content type: application/json.

Response

200 The updated domain (application/json)

Each item of records is a VerificationRecord:

Errors

Error bodies use the error envelope.

Example

curl -X PATCH "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/domains/dom_01k6x3d5kq0000g3q9sx5b8j0m" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "feedback_enabled": false
}'

Delete Domain in Pod

DELETE /v0/pods/{pod_id}/domains/{domain_id}

Same as Delete Domain (DELETE /v0/domains/{domain_id}), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_delete.

Path parameters

Response

200 Deleted (application/json)

Field Type Description
ok true required

Errors

Error bodies use the error envelope.

Example

curl -X DELETE "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/domains/dom_01k6x3d5kq0000g3q9sx5b8j0m" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
{
  "ok": true
}

Get Domain Connect Setup in Pod

GET /v0/pods/{pod_id}/domains/{domain_id}/domain-connect

Same as Get Domain Connect Setup (GET /v0/domains/{domain_id}/domain-connect), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_read.

Path parameters

Response

200 Domain Connect support for the domain (application/json)

Errors

Error bodies use the error envelope.

Example

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

Verify Domain in Pod

POST /v0/pods/{pod_id}/domains/{domain_id}/verify

Same as Verify Domain (POST /v0/domains/{domain_id}/verify), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_update.

Path parameters

Response

200 The domain after the check (application/json)

Each item of records is a VerificationRecord:

Errors

Error bodies use the error envelope.

Example

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

Get Zone File in Pod

GET /v0/pods/{pod_id}/domains/{domain_id}/zone-file

Same as Get Zone File (GET /v0/domains/{domain_id}/zone-file), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: domain_read.

Path parameters

Response

200 A BIND zone file with the records to publish (text/plain)

Headers:

Field Type Description
Content-Disposition string attachment; filename=".zone"

Errors

Error bodies use the error envelope.

Example

curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/domains/dom_01k6x3d5kq0000g3q9sx5b8j0m/zone-file" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"