---
title: "Domains"
description: "`domains.list`, `get` and `update`."
url: "https://openemail.uk/docs/sdk/domains"
area: "SDK"
category: "Mailbox"
---

# Domains

`domains.list`, `get` and `update`.

## Every method

**usage.ts**

```
const domains = await openemail.domains.list()
const domain = await openemail.domains.get('b3e1f0a4-6c2d-4e8a-9f17-2d5c8a0b4e6f')

console.log(domain.receiving.verified, domain.sending.status)
for (const address of domain.addresses) console.log(address.address, address.enabled)

const updated = await openemail.domains.update(domain.id, { trackingHost: 'links.acme.com' })
console.log(updated.tracking.status, updated.tracking.record?.name, updated.tracking.record?.value)

await openemail.domains.update(domain.id, { trackingHost: null })
```

Receiving and sending are two independent facts and are returned as two objects. `receiving.verified` means the domain’s MX brings its mail here and its ownership challenge is published. `sending` reports the outbound signing check: `status` is `verified`, `pending`, `failed`, `no_identity` or `unknown`, and `canSend` says whether a send from the domain would be accepted right now. A negative verdict older than a day is treated as unknown rather than as a refusal, so branch on `canSend` rather than on `status`.

`update` sets, checks again or removes the domain’s custom tracking domain, a subdomain such as `links.acme.com`, and resolves to the same `DomainDetailResource` as `get`. `tracking` reports it on every read. Until a check passes, `tracking.status` is `pending` and tracked links and the open pixel keep using the default OpenEmail host. Once one passes, it is `active` and new mail from the domain uses the tracking domain for both.

> `get` also lists the addresses on the domain. `addresses.list()` is the related call: every address THIS KEY may put in a From header, which is narrower.

## Parameters: domains.get

- `domainId` (string, required): The id from `domains.list`, a UUID minted when the domain was added, not the hostname, so `get('example.com')` finds nothing. The lookup is scoped to the key's own connection as well as to the id, so another workspace's domain is a 404 rather than a 403.

## Parameters: domains.update

- `id` (string, required): The same domain id `get` takes. `domains:write` is the scope it needs.
- `patch.trackingHost` (string | null, required): A subdomain of the domain, at most 512 characters, such as `links.acme.com`. It is trimmed and lowercased, and a leading `https://` or `http://`, a path and a trailing dot are stripped. A new value is validated, saved and checked in the same call. The value the domain already has runs the check again, unless the last one was less than 30 seconds ago. `null` or an empty string removes the tracking domain.

> A refused host throws an `OpenEmailApiError` naming `trackingHost` in `param`: 422 `invalid_tracking_host` for a name that cannot be used, such as one outside the domain, 409 `domain_not_verified` for a new host while `receiving.verified` is false and the domain’s `_openemail-challenge` TXT record is not published yet, and 409 `tracking_host_in_use` for a name another domain already uses, or when the tracking domain is managed by a different OpenEmail server. A key limited to specific addresses gets 422 `capability_unsupported`, because a tracking domain applies to every address on the domain.

## Response: DomainDetailResource

- `object` ('domain'): Always the string `domain`, on `list` rows and on this one alike.
- `id` (string): The domain's UUID. Stable for the life of the row, and the only handle the other domain calls accept.
- `domain` (string): The bare hostname, lowercased: `example.com`. Unique across the whole product, one owner per domain, so two workspaces cannot both claim it.
- `receiving.verified` (boolean): True once DNS showed the domain’s MX naming a host that brings its mail here and, where the row carries a challenge token, the matching `_openemail-challenge` TXT record. MX alone proves nothing, since every domain we receive for publishes the same hostnames, which is why the token exists, and why this flag is the gate inbound delivery checks before accepting mail.
- `receiving.verifiedAt` (string | null): When verification passed, ISO-8601. Null while it has not, and `verified` is derived from exactly this column, so the two can never disagree.
- `receiving.catchAll` (boolean): Whether any local-part is accepted. On by default for domains added since this became the rule; with it off only addresses named on the domain are accepted and the rest are rejected at SMTP time, so the sender gets a bounce rather than silence.
- `receiving.lastCheckedAt` (string | null): When DNS was last asked about this domain. Null means never looked, which reads very differently from a failure to somebody who added a domain a minute ago. This endpoint reports the stored result, never running a check of its own.
- `receiving.error` (string | null): Why the last check did not pass, in words the owner can act on: `No MX records yet. DNS changes can take a few minutes to spread.` is a typical one. Null once it passes, and stored rather than derived so a reload and the scheduled re-check say the same thing.
- `sending.status` ('verified' | 'pending' | 'failed' | 'no_identity' | 'unknown'): The outbound signing state as the last check saw it. Read from the stored check rather than probed on this request, so `sending.checkedAt` says how old it is.
- `sending.canSend` (boolean): Whether a send from this domain would be accepted right now. A negative verdict older than a day is treated as unknown rather than as a refusal, so this can be true while `status` is `pending`. Branch on this before a send: a false one means `emails.send` from this domain is refused with a 409 `domain_not_sendable`.
- `sending.checkedAt` (string | null): When the signing state was last checked, ISO-8601. Null means never, which reads very differently from a failure.
- `sending.error` (string | null): The last signing failure in words, or null once it passes.
- `sending.note` (string): One of five sentences, chosen by `sending.status`, saying what that state means in words a domain owner can act on. Prose for a human to read. Branch on `sending.canSend` rather than on this.
- `tracking` (DomainTracking): The domain’s custom tracking domain, on `list` rows and on this one alike, and what `update` changes.
- `tracking.host` (string | null): The tracking domain, such as `links.acme.com`, or null when none is set.
- `tracking.status` ('none' | 'pending' | 'active' | 'failed'): `none` means no tracking domain is set, `pending` means it has never passed a check, `active` means new mail uses it, and `failed` means it passed before and has since dropped out of use. An active host drops out after three failed checks in a row, or once its last passed check is more than 2 hours old.
- `tracking.active` (boolean): True exactly when `status` is `active`, which is when tracked links and the open pixel in new mail from the domain use the host.
- `tracking.target` (string): The address the CNAME record points at, prepared for this tracking domain alone. An empty string while `host` is null, and while the address for a new host is still being prepared.
- `tracking.record` ({ type: 'CNAME'; name: string; value: string } | null): The record to publish, named after `host` with `target` as its value. Null when there is no tracking domain, and while the address for a new host is still being prepared.
- `tracking.checkedAt` (string | null): When the host was last checked, ISO-8601. Null until the first check.
- `tracking.verifiedAt` (string | null): When a check last passed, ISO-8601. Null for a host that has never passed one.
- `tracking.error` (string | null): What the last check found, in words the domain owner can act on. Null when the last check passed or none has run yet. A host that has failed one or two checks is still `active` and carries the reason here.
- `addresses` (Array<{ address: string; enabled: boolean }>): Every address row on the domain, which is what `get` adds over a `list` row. It includes the rows delivery wrote itself under catch-all, and those stop being accepted the moment catch-all is turned off, so the array is not a list of what will receive.
- `addresses[].address` (string): The full address, rebuilt from the stored local-part and the hostname and lowercased, so it always matches the `domain` above rather than drifting from it.
- `addresses[].enabled` (boolean): False disables the address, and a disabled one is rejected even when catch-all is on. Every row is listed either way, so filter on this rather than reading the array as the set of working addresses.
- `createdAt` (string): When the domain row was added, ISO-8601. Not when it verified: that is `receiving.verifiedAt`, which can be null while this is set.
