Skip to the documentation
API

Add a domain

Adds a domain to the workspace and returns it with every DNS record it needs. The domain starts unverified, with its catch-all on, and verifies on its own once the records answer in public DNS.

POST/domains

Runs the real call on your workspace.

POST /domains

Adds a domain to the workspace and returns it with every DNS record it needs. The domain starts unverified, with its catch-all on, and verifies on its own once the records answer in public DNS.

The request

Parameters

domainstringrequired
A bare domain such as `acme.com` or `mail.acme.com`. It is trimmed and lowercased, and an international name is stored in its ASCII form. A scheme, a path or an address is refused.

Example

Needs domains:write, and a key with no address or domain restriction, because a new domain reaches the whole workspace.

curl
curl -X POST "$OE/domains" -H "$AUTH" -H "Content-Type: application/json" \  -d '{ "domain": "acme.com" }'
Response
{  "object": "domain",  "id": "b3e1f0a4-6c2d-4e8a-9f17-2d5c8a0b4e6f",  "domain": "acme.com",  "receiving": {    "verified": false,    "verifiedAt": null,    "catchAll": true,    "lastCheckedAt": "2026-09-25T10:00:02.000Z",    "error": "No MX records yet. DNS changes can take a few minutes to spread."  },  "sending": {    "status": "no_identity",    "canSend": false,    "checkedAt": null,    "error": null,    "note": "Sending is not set up yet. Publish the signing record listed for this domain, and sending switches on shortly after it answers in DNS."  },  "tracking": { "host": null, "status": "none", "active": false, "target": "", "record": null, "checkedAt": null, "verifiedAt": null, "error": null },  "storage": { "host": null, "status": "none", "active": false, "target": "", "record": null, "checkedAt": null, "verifiedAt": null, "error": null },  "addresses": [],  "createdAt": "2026-09-25T10:00:00.000Z",  "records": [    { "type": "MX", "name": "acme.com", "value": "inbound.mail.example", "priority": 10, "purpose": null, "status": "missing" },    { "type": "TXT", "name": "acme.com", "value": "v=spf1 include:_spf.openemail.uk ~all", "priority": null, "purpose": null, "status": "missing" },    { "type": "TXT", "name": "_openemail-challenge.acme.com", "value": "openemail-verify-4f1c9a2b7e8d6053a1c49e2b7d0c8f36", "priority": null, "purpose": "Proves you own this domain", "status": "missing" }  ],  "dmarc": null}

The example trims records. A real response lists every record the domain needs, including DMARC, the signing record and the return-path records, and GET /domains/{id} describes each field.

Publish the records exactly as given, then call POST /domains/{id}/verify or wait: the domain is checked again whenever it is read and by a background sweep, and domain.verified fires the moment it passes.

When it is refused

  • 409 domain_already_added: this workspace owner already has the domain.
  • 409 domain_claimed: another account has it.
  • 409 related_domain_owned: a parent or a subdomain of it belongs to another account, so it cannot be split between the two.
  • 409 operator_domain: the domain belongs to Nextmail itself.
  • 403 domain_allowance_reached: the plan holds no more domains. The message names the plan that holds more.
  • 422 capability_unsupported: the key is limited to particular addresses or domains.

Reference