---
title: "Add a domain"
description: "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."
url: "https://openemail.uk/docs/api/domains/create"
area: "API"
category: "Mailbox"
---

# 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`

## 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**

- `domain` (string, required): 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": "oe-verify=4f1c9a2b7e8d6053a1c4", "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 CNAMEs 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 OpenEmail 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.
