# Sending Domains

Source: https://molted.email/docs/domains

Add and verify sending domains with DKIM, SPF, and DMARC records.

Every email sent through Molted Email comes either from the platform's shared domain (`agent.molted.email` by default, which every account can use, Free included) or from a custom domain you have verified. A custom domain protects your own sender reputation and lets you send from your brand.

## Why verification is required

A mailbox on a custom domain that isn't verified (or isn't yours) can't send: its sends are blocked with `sender_domain_not_verified`. (`no_verified_domain` is the rarer case where no sender address can be resolved at all.) This prevents unauthorized sending and protects the platform's shared reputation. A domain can send once its status is `verified` (or `temporary_failure`, a transient provider state).

## Adding a domain

```bash title="CLI"
molted domains add yourdomain.com
```

```bash title="curl"
curl -X POST https://mailbox.molted.email/v1/agent/domains \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "yourdomain.com" }'
```

The response includes the DNS records you need to configure. `dnsRecords` is passed through from the sending provider (Resend), so read the exact names and values from your own response; the shape looks like this:

```json title="Response (abridged)"
{
  "id": "3b0f6c2e-8d1a-4f5e-9c7b-2a4d6e8f0a1b",
  "domain": "yourdomain.com",
  "status": "pending",
  "dnsRecords": [
    { "record": "DKIM", "type": "TXT", "name": "resend._domainkey.yourdomain.com", "value": "p=MIGfMA0...", "status": "not_started" },
    { "record": "SPF", "type": "MX", "name": "send.yourdomain.com", "value": "feedback-smtp.us-east-1.amazonses.com", "priority": 10, "status": "not_started" },
    { "record": "SPF", "type": "TXT", "name": "send.yourdomain.com", "value": "v=spf1 include:amazonses.com ~all", "status": "not_started" },
    { "record": "Receiving", "type": "MX", "name": "yourdomain.com", "value": "inbound-smtp.us-east-1.amazonaws.com", "priority": 10, "status": "not_started" }
  ],
  "receivingEnabled": true,
  "openTracking": false,
  "clickTracking": false,
  "trackingSubdomain": null,
  "firstSendAt": null,
  "warmupOverride": false,
  "maxPerMinute": null,
  "maxPerHour": null,
  "maxPerDay": null,
  "dmarc": { "...": "see DMARC below" },
  "createdAt": "2026-03-01T12:00:00Z",
  "updatedAt": "2026-03-01T12:00:00Z"
}
```

`id` is a UUID. `status` is copied from the provider: `not_started`, `pending`, `verified`, `failed` or `temporary_failure`. The DMARC record is not in `dnsRecords`; it's in the separate `dmarc` object (below).

You can also add and remove domains in the portal: **Settings > Domains**, or press **⌘K** and type `add domain` (or `remove domain`). Adding one opens its [DNS page](#the-dns-page-in-the-portal) with the records to set. Custom domains need a paid plan; on Free, mailboxes use the shared domain.

The shared sending domain (`agent.molted.email` by default) and any subdomain of it cannot be added as a custom domain (`400 Bad Request`) -- it belongs to the platform, not to any one tenant. A domain another tenant has already verified, or one already in your own workspace, is rejected with `409 Conflict`. Plan limits are a `400`: custom domains need a paid plan (Free 0, Solo 3, Team 10, Enterprise unlimited; a paid plan's 3-day trial allows 1), and past the cap you get `Domain limit reached`.

Adding, verifying and removing domains, changing tracking, skipping warmup and changing rate limits need a key with `manage` (or `admin`) scope on all mailboxes; reads need any valid key. The MCP server has no domain write tools: `list_domains` (no parameters) and the `molted://domains` resource show your domains, including `firstSendAt`, `warmupOverride` and the `maxPer*` limits, and `check_send_health` reports warmup caps.

## DNS records

### DKIM

A TXT record (on `resend._domainkey.<domain>`) that adds a cryptographic signature to your outgoing emails. Recipients use this to verify the email was sent by an authorized server and wasn't tampered with in transit.

### SPF

A TXT record, plus an MX record for bounce feedback, on the `send.` subdomain of your domain (the return-path domain) that authorizes Molted Email's sending servers. This tells recipient mail servers that our IPs are allowed to send email for your domain.

### DMARC

A TXT record that specifies what recipient servers should do with emails that fail DKIM or SPF checks. We recommend starting with a `p=none` policy and moving to `p=quarantine` or `p=reject` once you've confirmed everything works.

Every domain response includes a `dmarc` object with the exact record to publish, including a `rua=` pointing at Molted so aggregate reports are collected and parsed for you:

```json title="dmarc"
{
  "address": "dmarc@agent.molted.email",
  "rua": "mailto:dmarc@agent.molted.email",
  "record": {
    "type": "TXT",
    "name": "_dmarc.yourdomain.com",
    "value": "v=DMARC1; p=none; rua=mailto:dmarc@agent.molted.email"
  }
}
```

See [DMARC Reports](https://molted.email/docs/dmarc.md) for what lands in your account and how attribution works.

### MX

An MX record that routes inbound email to Molted Email for receiving on this domain.

### One-click DNS setup

If your DNS provider supports the [Domain Connect](https://www.domainconnect.org/) protocol, you can skip manual record configuration entirely. Run `dns-check` to find out:

```bash title="CLI"
molted domains dns-check <domainId>
```

```bash title="curl"
curl "https://mailbox.molted.email/v1/agent/domains/DOMAIN_UUID/domain-connect" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**If one-click setup is available**, the response includes a redirect URL. Open this URL to configure all DNS records automatically at your provider -- no manual copy-pasting required.

```json title="Response (supported)"
{
  "supported": true,
  "redirectUrl": "https://your-dns-provider.com/v2/domainTemplates/providers/molted.email/services/moltedemail/apply?..."
}
```

After completing the redirect flow, run `molted domains verify <domainId>` to confirm the records are in place.

**If one-click setup is not available**, you need to add the DNS records manually at your registrar. Use `molted domains get <domainId>` to see the required records and their current verification status.

```json title="Response (not supported)"
{
  "supported": false,
  "reason": "DNS provider does not support Domain Connect"
}
```

This step is optional. If you prefer to add DNS records manually, skip `dns-check` and go straight to adding records at your registrar.

Supported providers include Cloudflare, Vercel, and any other DNS host that implements Domain Connect.

## Verification

After adding the DNS records at your registrar, verify them:

```bash title="CLI"
molted domains verify <domainId>
```

DNS propagation typically takes a few minutes but can take up to 48 hours. Re-run `verify` if it initially fails.

Once verified, the domain status changes to `verified` and you can start sending.

## The DNS page in the portal

A person does all of the above on one page in the [portal](https://molted.email/docs/portal.md): `/app/dns/<domainId>`. It opens when you add a domain, from a domain's row in **Settings > Domains**, and from the **Verify DNS for yourdomain.com** card on Home (its **Show records** and **Connect DNS** buttons both open this page).

- **The records.** Each DNS record to add is listed with its type, name, value and status (`verified`, `not found yet` or `failed`), with a **Copy** button for the value. A count under them says how many are found. When the statuses are cached and may have moved on (`dnsRecordsStale`), the page says "These statuses may be a little behind. Check again to refresh them."
- **Connect automatically.** If your DNS provider supports [Domain Connect](#one-click-dns-setup), a button sends you to the provider to approve the records, and says it adds those records and nothing else.
- **Check again** checks the records now and refreshes the page (the portal's `molted domains verify`). DNS changes can take a few minutes, sometimes longer, to reach every provider.
- **Coming back from Domain Connect.** When the provider returns you to the page, it checks once by itself, so you do not have to press **Check again**. If you declined, or the provider could not apply the records, the page says "Domain Connect couldn't finish automatically. Add the records below, or try again."
- **Verified.** The page then reads "Verified. Mail from yourdomain.com can send." and keeps **Check again**.
- **DMARC reporting.** A collapsed section shows the DMARC record to publish, the `dmarc.record` from [above](#dmarc), with a **Copy** button. It is not one of the records the domain needs to verify, and the page notes that reports are only collected once the domain is verified. The reports themselves are not shown in the portal, see [DMARC Reports](https://molted.email/docs/dmarc.md).

## Managing domains

```bash title="CLI"
# List all domains
molted domains list

# Get domain details including per-record DNS status
molted domains get <domainId>
```

When a domain is in a terminal `status` (`verified`, or `failed`, `failure` or `temporary_failure`, which reconcile to `failed`), per-record `dnsRecords[*].status` values stuck at `pending` / `not_started` are reconciled to match the parent status and the response includes `dnsRecordsStale: true`. This avoids the misleading case where the cached per-record statuses say "pending" while the domain itself has already failed verification. To pull live statuses straight from the provider, run `domains verify`.

### Removing a domain

```bash title="CLI"
molted domains remove <domainId>
```

> **Warning:** **Destructive action.** Removing a domain deletes it at the sending provider and deletes its configuration here; the response is `{ "deleted": true }`. Mailboxes on the domain are left as they are, but their sends fail with `sender_domain_not_verified` until the domain is re-added and verified. There is no confirmation prompt -- the domain is removed immediately. (From the portal, removing needs the human owner's session.)

## Domain rate limits

Each domain can have optional per-minute, per-hour, and per-day send caps. None are set by default (`null` means no cap); the windows are UTC calendar minutes, hours and days, not rolling windows. View current limits and usage:

```bash title="CLI"
molted domains rate-limits <domainId>
```

```bash title="curl"
curl "https://mailbox.molted.email/v1/agent/domains/DOMAIN_UUID/rate-limits" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json title="Response"
{
  "maxPerMinute": null,
  "maxPerHour": 100,
  "maxPerDay": 1000,
  "perMinute": null,
  "perHour": { "used": 12, "limit": 100, "remaining": 88 },
  "perDay": { "used": 47, "limit": 1000, "remaining": 953 }
}
```

The top-level `maxPerMinute|Hour|Day` fields mirror what the `set` endpoint returns, so the same field names work whether you're configuring or observing. The `perMinute|Hour|Day` objects carry usage detail and are `null` for windows with no custom limit.

A send over a cap is blocked with `domain_minute_limit_exceeded`, `domain_hourly_limit_exceeded` or `domain_daily_limit_exceeded` (see [Errors](https://molted.email/docs/errors.md#warmup-and-domain-throttling)).

### Set custom rate limits

```bash title="CLI"
molted domains rate-limits set <domainId> --max-per-hour 100 --max-per-day 1000
```

| Flag | Type | Description |
| - | - | - |
| `--max-per-minute <n>` | `number` | Max sends per minute. |
| `--no-max-per-minute` | `boolean` | Remove custom per-minute limit (revert to default). |
| `--max-per-hour <n>` | `number` | Max sends per hour. |
| `--no-max-per-hour` | `boolean` | Remove custom per-hour limit (revert to default). |
| `--max-per-day <n>` | `number` | Max sends per day. |
| `--no-max-per-day` | `boolean` | Remove custom per-day limit (revert to default). |

Use `--no-max-per-*` flags to clear a limit: that sends `null`, which removes the cap (unlimited), there is no platform default to revert to. The API equivalent is `PATCH /v1/agent/domains/:domainId/rate-limits` with any of `maxPerMinute`, `maxPerHour`, `maxPerDay` (an integer of 0 or more, or `null`; `0` blocks every send), returning `{ id, domain, maxPerMinute, maxPerHour, maxPerDay }`.

**Raising or clearing a limit needs a human.** Agents may lower a limit or set one where there was none. Raising one, or clearing it to unlimited, is a [loosening](https://molted.email/docs/errors.md#loosening-a-control): on the default (`enforce`) human gate an API key gets `403 human_decision_required` with `kind: "domain-rate-limits"`, a `decisionUrl` to hand to a person, and `loosens` naming the fields (e.g. `["maxPerHour"]`). The CLI prints that link and exits 1.

## Open & click tracking

Open and click tracking are **off by default** for every domain. You can opt in per domain -- either when adding the domain or later.

Tracking only becomes active once a tracking subdomain (e.g. `links.yourdomain.com`) is configured and its CNAME record is verified. When you enable tracking without naming a subdomain, it defaults to `links`.

```bash title="CLI"
# Enable at creation
molted domains add yourdomain.com --click-tracking --open-tracking

# Enable on an existing domain
molted domains tracking <domainId> --click --open

# Disable click tracking, keep open tracking
molted domains tracking <domainId> --no-click
```

```bash title="curl"
curl -X PATCH "https://mailbox.molted.email/v1/agent/domains/DOMAIN_UUID/tracking" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "openTracking": true, "clickTracking": true, "trackingSubdomain": "links" }'
```

The response includes a refreshed `dnsRecords` array with the new `Tracking` CNAME you need to add at your registrar. Add it, then run `molted domains verify <domainId>` to activate tracking.

| Flag (`add`) | Flag (`tracking`) | Description |
| - | - | - |
| `--open-tracking` | `--open` / `--no-open` | Insert a tracking pixel to measure opens. |
| `--click-tracking` | `--click` / `--no-click` | Rewrite links through your tracking subdomain to measure clicks. |
| `--tracking-subdomain <name>` | `--subdomain <name>` | Tracking subdomain label (defaults to `links`). |

> **Warning:** **Click tracking affects deliverability.** It rewrites every link in your emails to redirect through your tracking subdomain. Leave it off for purely transactional mail unless you need click metrics. A tracking subdomain can be changed but never removed once set, and changes are limited to once every 24 hours.

## Domain warmup

New custom domains start with reduced daily send limits to build sender reputation gradually. The warmup schedule, by whole days since the domain's first send:

| Days since first send | Daily limit |
| - | - |
| 0 - 6 | 100 emails |
| 7 - 13 | 500 emails |
| 14 - 27 | 2,000 emails |
| 28+ | 10,000 emails (stays in place until warmup is skipped) |

Day 0 lasts until the first send. Days are UTC dates. Sends from the shared domain are exempt. The cap is per tenant and sender domain, not per mailbox.

What happens past the day's cap depends on the send:

- A marketing (non-transactional) send is **deferred** to the next day with room, up to 30 days ahead: the response has `status: "scheduled"` with reason `warmup_defer` and a `scheduledAt`. Past the 30-day horizon it's blocked with `warmup_horizon_exceeded`.
- A transactional send (a template id starting with `_`), a send with an explicit `scheduledAt`, or one with `deferOnWarmup: false` is blocked with reason `warmup_limit`.

### Check warmup status

```bash title="CLI"
molted domains warmup <domainId>
```

```bash title="curl"
curl "https://mailbox.molted.email/v1/agent/domains/DOMAIN_UUID/warmup" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json title="Response"
{
  "domainId": "3b0f6c2e-8d1a-4f5e-9c7b-2a4d6e8f0a1b",
  "domain": "yourdomain.com",
  "warmupActive": true,
  "skipped": false,
  "firstSendAt": "2026-03-02T09:00:00Z",
  "currentDay": 9,
  "dailyLimit": 500,
  "sendsToday": 42
}
```

For a domain that isn't verified yet the response has `warmupActive: false`, `reason: "domain_not_verified"`, and `null` for `dailyLimit` and `sendsToday`.

### Skip warmup

If you have an established sending history and are migrating a domain to Molted, you can skip the warmup ramp.

```bash title="CLI"
molted domains warmup skip <domainId>
```

```bash title="curl"
curl -X POST "https://mailbox.molted.email/v1/agent/domains/DOMAIN_UUID/warmup/skip" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" -d '{}'
```

The response is `{ "domainId": "...", "domain": "yourdomain.com", "warmupSkipped": true }`. Skipping warmup removes the daily warmup restriction entirely (any per-domain rate limits you set still apply), and it **can't be undone**: there is no un-skip. Only do this if your domain already has a strong sending reputation -- skipping warmup on a cold domain can hurt deliverability.

**Skipping warmup needs a human.** It is always a [loosening](https://molted.email/docs/errors.md#loosening-a-control): on the default (`enforce`) human gate an API key (including the CLI) gets `403`:

```json title="Response (403)"
{
  "error": "human_decision_required",
  "message": "Agents may tighten controls but not loosen them. This change needs a human: share the decisionUrl with a person on your team; they can make it in the Molted portal.",
  "decisionUrl": "https://molted.email/app/decide/domain-warmup/DOMAIN_UUID",
  "kind": "domain-warmup",
  "id": "DOMAIN_UUID",
  "reason": "enforce",
  "loosens": ["warmup"]
}
```

On a workspace whose human gate is `warn` the skip goes through with a `Molted-Deprecation: human-gate` header, and on `off` it simply goes through. The key also needs `manage` scope first (`403 insufficient_scope`). The portal has no warmup-skip control yet: the `decisionUrl` page explains the request to the person but can't carry it out, so on an `enforce` workspace a skip isn't possible today.

## Related

- [One-Click Domain Setup with Domain Connect](https://molted.email/blog/one-click-domain-setup-with-domain-connect) -- skip manual DNS configuration with supported providers
- [DMARC Reports](https://molted.email/docs/dmarc.md) -- upload and analyze DMARC aggregate reports
- [Sender Addresses & Domain Rate Limits](https://molted.email/docs/sender-addresses.md) -- configure sender address and per-domain throttling
- [Rate Limits](https://molted.email/docs/rate-limits.md) -- tenant-level rate limits and usage
