# Sending from your own domain

For `ctx.email.send` itself, see [Sending email](email.md). Until you add a domain, your app's emails come from `<project>@mail.flatstack.app` with your reply-to address. To send from your own domain, add it and create one DNS record.

## Add the domain

```bash
curl -X POST https://api.flatstack.dev/v1/projects/$PROJECT/email-domains \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"domain":"example.com"}'
```

The response includes `record`, the one record to create at your DNS host:

| Name | Type | Value |
| --- | --- | --- |
| `flatstack._domainkey.example.com` | CNAME | `<id>.dkim.mail.flatstack.app` |

That record proves you control the domain and lets us sign your emails, so they pass DMARC checks. You don't need to change SPF.

A subdomain works too, such as `mail.example.com`. The name is then `flatstack._domainkey.mail.example.com`.

## How checking works

We check DNS every minute for the first hour, then every hour for up to 72 hours. To check right away, call `POST …/email-domains/example.com/check`.

| Status | Meaning |
| --- | --- |
| `pending` | Waiting for the record, or for the mail provider to confirm it. Emails use the default address. |
| `verified` | Emails can come from this domain. We recheck the record once a day. |
| `failed` | No correct record within 72 hours. Checks stop; fix the record, then check again. |
| `lost` | The record was removed, or another project's record now owns the domain. Emails use the default address with your `from` as the reply-to, and owners get an email; restore the record to resume. |

When something's wrong, `diagnostics` says what to fix. These are the common ones:

| Code | What to do |
| --- | --- |
| `not_found` | Add the record. New records can take a few minutes to appear. |
| `doubled_name` | Your DNS host adds the domain for you. Enter only `flatstack._domainkey` (plus any subdomain) as the name. |
| `txt_instead_of_cname` | Delete the TXT record and add a CNAME instead. |
| `proxied` | Set the record to DNS only. In Cloudflare, that's the grey cloud. |
| `points_elsewhere`, `wrong_target` | Change the record's value to the one in `record.value`. |
| `awaiting_provider` | Nothing to do. The record is right, and the mail provider confirms it within minutes. |
| `in_use` | Another project sends from this domain. It moves here once your record points at this project's value. |

`dnsProvider` names your DNS host when we recognise it, so your agent can point you at its instructions.

## Recommended: DMARC

Once the domain is verified, `dmarc` shows your existing DMARC policy. If you have none, it suggests one:

| Name | Type | Value |
| --- | --- | --- |
| `_dmarc.example.com` | TXT | `v=DMARC1; p=none` |

This record is optional. We never change an existing one.

## Moving a domain between projects

A domain sends for one project at a time. Adding it to a second project changes nothing until you point the CNAME at the second project's value. Once it verifies there, the first project goes back to the default address and its owners get an email.

## Rotating the key

`POST …/email-domains/example.com/rotate-key` replaces the signing key without any DNS change on your side. For about five minutes, while DNS caches pick up the new key, emails from the domain go from the default address with your `from` as the reply-to. Then they send from the domain again.

## Removing a domain

`DELETE …/email-domains/example.com` with `{ "confirm": true }` stops sending from the domain at once, and the action is recorded in your audit log. Then delete the CNAME at your DNS host. Until you do, it points at a name that no longer exists, and nobody can reuse that name.

## API

| Request | Who | What it does |
| --- | --- | --- |
| `GET /v1/projects/{id}/email-domains` | Everyone | The project's sender domains. |
| `POST /v1/projects/{id}/email-domains` | Owners, admins, tokens with `projects:write` | `{ "domain" }`. Returns `201` with the record to add, or `200` with the existing domain. |
| `GET /v1/projects/{id}/email-domains/{domain}` | Everyone | Status, the record, the latest diagnostics and DMARC findings. |
| `POST /v1/projects/{id}/email-domains/{domain}/check` | Owners, admins, tokens with `projects:write` | Checks now. |
| `POST /v1/projects/{id}/email-domains/{domain}/rotate-key` | Owners, admins, tokens with `projects:write` | New signing key under the same record. |
| `DELETE /v1/projects/{id}/email-domains/{domain}` | Owners, admins, tokens with `projects:write` | Needs `confirm: true`. |

A domain that can't send, such as an IP address or a `flatstack.app` subdomain, returns `400 email_domain_invalid`.