---
title: "Domains"
description: "Verify a sending domain with a TXT challenge, then publish the SPF, DKIM, and DMARC records that decide whether mail lands."
canonical_url: "https://www.agent-identity.dev/docs/mail/domains"
markdown_url: "https://www.agent-identity.dev/docs/mail/domains.md"
last_updated: "2018-10-20"
x_farming_labs_generated_preamble: true
agent:
  tokenBudget: 800
  task: "Add and verify a sending domain, then publish the deliverability records the configured provider requires."
  outcome: "GET /v1/domains shows the domain verified with a non-null verified_at, and every record from GET /v1/domains/:id/deliverability is published in DNS."
  appliesTo:
    package:
      - "@aid/api"
  prerequisites:
    - "Authority to create TXT records on the domain."
    - "An org key; domain management is not available to agent keys."
  files:
    - "apps/api/src/services/domains.ts"
  sideEffects:
    - "Verification performs a live DNS TXT lookup against the domain at the moment it is called."
  verification:
    - description: "Publish the challenge TXT record, wait for propagation, then call verify."
      expect: "POST /v1/domains/:id/verify returns the domain with status verified and a non-null verified_at."
  rollback:
    - "DELETE /v1/domains/:id removes the domain. Remove the TXT records from DNS separately."
  failureModes:
    - symptom: "Verification keeps failing although the record is published."
      resolution: "The lookup reads live DNS with no cache of its own, but resolvers still hold the old negative answer. Confirm with `dig +short TXT <record-name>` from the API host, then retry."
    - symptom: "Mail sends but lands in spam."
      resolution: "Domain verification only proves ownership. SPF, DKIM, and DMARC are separate records from GET /v1/domains/:id/deliverability and must each be published."
    - symptom: "The DKIM record value comes back null."
      resolution: "Expected for some providers, which generate a unique DKIM key per domain rather than a shared one. Publish the records that GET /v1/domains/:id/deliverability does return, and check back once the domain is verified."
---

# Domains
URL: /docs/mail/domains
LLM index: /llms.txt
Description: Verify a sending domain with a TXT challenge, then publish the SPF, DKIM, and DMARC records that decide whether mail lands.
Related: /docs/mail/sending, /docs/mail/receiving, /docs/getting-started/quickstart

<!-- farming-labs:agent-contract:start -->
## Agent Contract

Task: Add and verify a sending domain, then publish the deliverability records the configured provider requires.
Outcome: GET /v1/domains shows the domain verified with a non-null verified_at, and every record from GET /v1/domains/:id/deliverability is published in DNS.

### Applies To

- Package: `@aid/api`

### Prerequisites

- Authority to create TXT records on the domain.
- An org key; domain management is not available to agent keys.

### Files

- `apps/api/src/services/domains.ts`

### Side Effects

- Verification performs a live DNS TXT lookup against the domain at the moment it is called.

### Verification

- Publish the challenge TXT record, wait for propagation, then call verify.
  - Expected: POST /v1/domains/:id/verify returns the domain with status verified and a non-null verified_at.

### Rollback

- DELETE /v1/domains/:id removes the domain. Remove the TXT records from DNS separately.

### Failure Modes

- Verification keeps failing although the record is published. — Recovery: The lookup reads live DNS with no cache of its own, but resolvers still hold the old negative answer. Confirm with `dig +short TXT <record-name>` from the API host, then retry.
- Mail sends but lands in spam. — Recovery: Domain verification only proves ownership. SPF, DKIM, and DMARC are separate records from GET /v1/domains/:id/deliverability and must each be published.
- The DKIM record value comes back null. — Recovery: Expected for some providers, which generate a unique DKIM key per domain rather than a shared one. Publish the records that GET /v1/domains/:id/deliverability does return, and check back once the domain is verified.
<!-- farming-labs:agent-contract:end -->

# Domains

Sending from your own domain takes two separate things, and conflating them is the usual
reason mail ends up in spam: **verification** proves you control the domain, and
**deliverability records** convince receiving servers to trust the mail.

## Verify ownership

```bash title="terminal"
curl -sS -X POST https://api.agent-identity.dev/v1/domains \
  -H "authorization: Bearer $AID_KEY" \
  -H 'content-type: application/json' \
  -d '{"domain":"yourdomain.com"}'
```

```json
{
  "id": "7b3e…",
  "domain": "yourdomain.com",
  "status": "pending",
  "verification": {
    "record_type": "TXT",
    "record_name": "yourdomain.com",
    "record_value": "aid-verify=9f2c…"
  },
  "verified_at": null,
  "created_at": "2026-09-27T10:00:00.000Z"
}
```

Publish that TXT record, wait for propagation, then:

```bash title="terminal"
curl -sS -X POST "https://api.agent-identity.dev/v1/domains/$DOMAIN_ID/verify" \
  -H "authorization: Bearer $AID_KEY"
```

Verification does a live DNS lookup each time it is called, so just retry after
propagation rather than re-adding the domain.

## Deliverability

```bash title="terminal"
curl -sS "https://api.agent-identity.dev/v1/domains/$DOMAIN_ID/deliverability" \
  -H "authorization: Bearer $AID_KEY"
```

The guidance is tailored to the configured provider, because the records differ:

```json
{
  "provider_kind": "resend",
  "records": [
    {
      "purpose": "spf",
      "record_type": "TXT",
      "record_name": "yourdomain.com",
      "record_value": "v=spf1 include:amazonses.com ~all",
      "note": "Resend sends via Amazon SES infrastructure — this is their documented required SPF include."
    },
    {
      "purpose": "dkim",
      "record_type": "TXT",
      "record_name": "resend._domainkey.yourdomain.com",
      "record_value": null,
      "note": "Resend generates a unique DKIM key per domain…"
    },
    {
      "purpose": "dmarc",
      "record_type": "TXT",
      "record_name": "_dmarc.yourdomain.com",
      "record_value": "v=DMARC1; p=none; rua=mailto:postmaster@yourdomain.com",
      "note": "Start with p=none…"
    }
  ]
}
```

A `null` `record_value` means the value is not knowable from here — DKIM keys are
generated per domain by the provider, so fetch that one from the provider's own
dashboard.

Start DMARC at `p=none`. It monitors without rejecting anything, which lets you confirm
legitimate mail passes SPF and DKIM before you tighten to `quarantine` or `reject`. Going
straight to `p=reject` on an unverified setup is how you silently lose real mail.

## Receiving

Verification and deliverability cover *sending*. To also receive, point the domain's MX
records at your provider and configure the inbound webhook — see
[Receiving mail](/docs/mail/receiving).

## Managing

`GET /v1/domains` lists them with status and `verified_at`. `DELETE /v1/domains/:id`
removes one; the DNS records are yours to clean up.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
