---
title: "Add a custom send domain"
method: POST
path: "/v1/domains"
tags: ["Domains"]
---

# Add a custom send domain

`POST /v1/domains`

## Request

- Base URL: `https://api.mails.ai`
- URL: `https://api.mails.ai/v1/domains`
- Auth: HTTP bearer

**Requires scope:** `manage`. Registers a bring-your-own sending domain (paid plans). Returns the DNS records to create at your registrar — records-only verification, no nameserver changes. Agents whose address is on a VERIFIED custom domain send under their own identity instead of the shared workspace subdomain. A domain that is already added, to this workspace or to another, is refused with 400 `duplicate_resource`.

## Request body

- CreateDomainRequest
  - `domain` string, required: The domain to send from, such as `mail.acme.com`, 4 to 253 characters, lowercased and without a trailing dot. A name that is not a valid hostname, is under `mails.ai`, or is already added to any workspace is refused with 400.

## Response `201`

The registered domain with the DNS records to create.

- Domain
  - `id` string, required: Domain id (`dom_…`).
  - `domain` string, required: The custom sending domain, lowercased.
  - `status` 'pending' | 'verifying' | 'verified' | 'failed', required: pending → records issued; verifying → checks in progress; verified → agents on this domain send under their own identity; failed → 20 verify calls in a row did not pass. Not final: a failed domain is still re-checked automatically (less often the longer it fails, at most a day apart) and verifies by itself once its records resolve; verify still checks at once, and the count starts over on success or after a day without a verify call.
  - `provider` string: Backing sending infrastructure for this domain.
  - `dns_records` DnsRecord[], required: The records to create at your DNS host. Each record's `verified` says whether the last check found it, and every `required` one must be found for the domain to verify.
    - `type` 'TXT' | 'CNAME' | 'MX', required: DNS record type to create.
    - `host` string, required: Fully-qualified record host/name to create at your DNS provider.
    - `value` string, required: Exact record value. Copy verbatim.
    - `purpose` 'spf' | 'dkim' | 'dmarc' | 'return_path' | 'tracking' | 'ownership', required: What the record is for: `dkim` (the key that signs your mail), `return_path` (the bounce address), `spf` and `dmarc` (your domain's sender policy), `tracking` (link tracking), or `ownership` (any other record the sending provider asks for, such as proof you control the domain).
    - `required` boolean, required: Required records gate verification; optional ones improve deliverability/reporting.
    - `verified` boolean, required: Whether the record was observed in DNS on the last check.
    - `instruction` string: How to create this record, word for word from the provider. For SPF and DMARC it says when to merge the value into a record the domain already publishes instead of replacing it, because replacing one can break the domain's existing mail.
    - `merge_token` string: The part to add to an existing record when merging (for SPF, e.g. `ip4:…`). A merged SPF record that contains it verifies.
  - `fail_reason` string, nullable: Why the last verification did not pass, when it didn't.
  - `last_check_at` string, date-time, nullable: When the records were last checked: by a verify call, by the automatic re-checks of unverified domains, or by the daily re-check of verified ones. Null before the first check.
  - `verified_at` string, date-time, nullable: When the domain first verified. Null until then, and cleared again when the daily re-check of verified domains (06:40 UTC) finds a required record gone.
  - `created_at` string, date-time, required: When the domain was added (UTC).

## Other responses

- `400`: Invalid request — malformed JSON or a field failed validation. Also `feature_not_enabled` when this server has a feature switched off (custom domains before they are available).
- `401`: Missing or invalid API key.
- `402`: Payment required: a plan limit (`plan_limit_exceeded`) or a feature the current plan does not include (`feature_not_enabled`). The error object carries `upgrade_url`, the dashboard page where the plan can be changed.
- `403`: The API key lacks the required scope (`insufficient_scope`), or the token belongs to an app connected by sign-in and this operation is closed to connected apps (`connected_app_not_allowed`).
- `500`: Internal server error.

---

[API](https://skmtc.dev/mails/apis/mails-ai-api.md) · [All operations](https://skmtc.dev/mails/apis/mails-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mails/apis/mails-ai-api/revisions/4d4930a8c33b?raw)
