# Custom sending domain

Send invoice emails from your own domain instead of BeeL.'s address, and choose the sender and default content of each company.

By default, invoice emails go out from a BeeL. address with the company's trade name as the sender name. With a custom sending domain they go out from an address on a domain you own, such as `facturas@mail.example.com`. Each company can use the account's default domain, pick another one, or stay on BeeL.'s address.

The domain belongs to the account, not to a company, and it is the same in Test and Live.

| What | Operation |
|---|---|
| List the account's domains and the limit | [`GET …/email-domains`](/emails/listAccountEmailDomains) |
| Register a domain | [`POST …/email-domains`](/emails/createAccountEmailDomain) |
| Read one domain and its DNS records | [`GET …/email-domains/{email_domain_id}`](/emails/getAccountEmailDomain) |
| Ask for a new DNS check | [`POST …/email-domains/{email_domain_id}/verify`](/emails/verifyAccountEmailDomain) |
| Make it the account's default | [`PUT …/email-domains/{email_domain_id}/default`](/emails/setAccountEmailDomainDefault) |
| Remove it | [`DELETE …/email-domains/{email_domain_id}`](/emails/deleteAccountEmailDomain) |
| Read or set a company's sender and default content | [`GET`](/emails/getCompanyEmailConfiguration) / [`PUT /v1/companies/{company_id}/email-configuration`](/emails/updateCompanyEmailConfiguration) |

Domain paths hang off `/v1/accounts/{account_id}`. Reading needs the `email-domains:read` scope and writing needs `email-domains:write`.

## Before you start

- **The add-on.** Custom sending domains are an Enterprise add-on, not part of the self-service plans: [talk to sales](https://beel.es/ventas) to add it. Without it, registering or verifying a domain answers `403` [`FEATURE_NOT_AVAILABLE`](/errors/FEATURE_NOT_AVAILABLE), and the list shows `max_email_domains: 0`. Reading, deleting and choosing the default still work.
- **A live API key for writes.** Domains are not scoped to an environment, so registering, verifying, deleting or changing the default with a test key answers `403` [`LIVE_CREDENTIAL_REQUIRED`](/errors/LIVE_CREDENTIAL_REQUIRED). Reads work with either key.
- **Access to the domain's DNS**, to publish the records BeeL. gives you.

<Steps>

<Step>

### Register the domain [#register]

Send the bare domain in `domain`: `mail.example.com`, without `@`, scheme or path. BeeL. trims it, lowercases it and drops a trailing dot. A subdomain used only for sending, such as `mail.` or `facturas.`, keeps your main domain's mail setup untouched.

The answer is `201` with the domain in `PENDING` and the DNS records to publish in `dns_records`. Each record has a `type` (`MX`, `TXT` or `CNAME`), a `name` relative to your domain, a `value`, a `priority` for `MX`, and its own `status`.

If `dns_records` comes back empty, registration is still finishing: read the domain again in a few seconds.

The first domain of the account becomes its default automatically.

</Step>

<Step>

### Publish the DNS records [#dns]

Add every record in `dns_records` at your DNS provider, exactly as given. If your DNS provider proxies traffic, publish these records as DNS-only.

</Step>

<Step>

### Verify [#verify]

Call `POST …/verify` once the records are published. It asks for a new check and answers `202` with the current state, usually still `PENDING`: verification is asynchronous. The result arrives with the [`email_domain.verified`](/webhook-events/onEmailDomainVerified) or [`email_domain.failed`](/webhook-events/onEmailDomainFailed) webhook, or the next time you read the domain. DNS changes can take a while to propagate, so you can call `verify` again later.

A domain that is never verified is removed 72 hours hours after it was registered, at the next daily maintenance run. You receive [`email_domain.removed`](/webhook-events/onEmailDomainRemoved) with `reason: EXPIRED`, and you can register it again.

</Step>

<Step>

### Choose the sender of each company [#company-sender]

Every company starts by inheriting the account's default domain, with `facturas` as the part before the `@`. To change it, call `PUT /v1/companies/{company_id}/email-configuration` with a `sender`:

| `sender.source` | Sends from |
|---|---|
| `ACCOUNT_DEFAULT` | The account's default domain |
| `EMAIL_DOMAIN` | The domain in `email_domain_id` |
| `BEEL` | BeeL.'s address, even if the account has a verified domain |

`local_part` sets the part before the `@` and cannot be combined with `BEEL`. Choosing a domain that is not verified yet is allowed. `sender` is required and replaces what was there: anything you leave out goes back to the account default with `facturas`. The call needs the `companies:write` scope, a live key and an `Idempotency-Key`.

</Step>

</Steps>

## Which address an email actually uses [#effective-sender]

`GET …/email-configuration` returns `sender.effective`, the address the next invoice email of that company will use, and a `reason`:

| `reason` | Means |
|---|---|
| `CUSTOM_DOMAIN` | It goes out from your domain |
| `BEEL_SELECTED` | The company chose BeeL.'s address |
| `NO_DOMAIN` | Neither the company nor the account has a domain |
| `ADDON_INACTIVE` | The account does not have the add-on |
| `DOMAIN_NOT_VERIFIED` | No candidate domain is verified yet |

The candidates are the company's chosen domain first and then the account's default; the first one that is `VERIFIED` is used. If none is, the email goes out from BeeL.'s address. Sending never stops because of a domain: a domain that is pending, fails its check or is removed only changes the address, and `reason` tells you why.

If a domain is rejected at the moment of sending, the same email is sent again from BeeL.'s address and the domain is marked `FAILED`, which sends `email_domain.failed`.

The sender name and the reply-to address do not change: the sender name is the company's trade name and replies go to the company's email.

## Default subject, message and recipients [#defaults]

The same `PUT …/email-configuration` takes `defaults` for the company's invoice emails: `subject`, `message`, `cc` and `additional_recipients` (always added to every invoice email). Leave `defaults` out to keep them, send `null` to clear them, or send an object to replace all of them. Setting them needs the email customization feature of your plan; without it the call answers `403` [`FEATURE_NOT_AVAILABLE`](/errors/FEATURE_NOT_AVAILABLE) and the sender is not changed either.

Subject and message accept placeholders written in braces, such as `{invoiceNumber}` or `{customerName}`, which are filled in when the email is sent. [`GET /v1/email-template-variables`](/emails/listEmailTemplateVariables) lists them. A placeholder that is not in the list is sent as written.

## Webhooks [#webhooks]

| Event | When |
|---|---|
| [`email_domain.verified`](/webhook-events/onEmailDomainVerified) | The domain passes its check |
| [`email_domain.failed`](/webhook-events/onEmailDomainFailed) | It fails it. `previous_status: PENDING` means it never verified; `VERIFIED` means it stopped passing |
| [`email_domain.removed`](/webhook-events/onEmailDomainRemoved) | It is removed. `reason` is `EXPIRED`, `DELETED_BY_USER` or `DELETED_AT_PROVIDER` |

Each event fires once per change, only to the Live endpoints of the account that owns the domain. They carry no `company_id`, because a domain belongs to the account.

## Gotchas

- **Deleting a domain in use is allowed.** Companies that had chosen it go back to `ACCOUNT_DEFAULT` and keep their `local_part`. If it was the default, the account is left without one, and email goes out from BeeL.'s address until you set another.
- **A default can be pending.** You can make a `PENDING` or `FAILED` domain the default; email only leaves from it once it is `VERIFIED`.
- **The name may stay taken for a while.** A deleted domain is usually free at once, but registering it again can occasionally answer [`EMAIL_DOMAIN_UNAVAILABLE`](/errors/EMAIL_DOMAIN_UNAVAILABLE) for a while. If it persists, contact support.
- **Saved templates use English placeholders.** Templates saved with the older Spanish placeholders (`{numeroFactura}` and the like) have been rewritten with their English names, so reading them back shows `{invoiceNumber}`. The Spanish names are still filled in if you send them.

| Case | Error | Status | Why |
|---|---|---|---|
| registering a value that is not a full domain name | [`EMAIL_DOMAIN_INVALID`](/errors/EMAIL_DOMAIN_INVALID) | `422` | The value is not a full domain name: it needs at least two labels, each up to 63 characters, a non-numeric top-level domain and no more than 253 characters in all. |
| registering BeeL.'s domain or a public mailbox domain | [`EMAIL_DOMAIN_RESERVED`](/errors/EMAIL_DOMAIN_RESERVED) | `422` | The domain, or a domain it is a subdomain of, is BeeL.'s own or a public mailbox provider (Gmail, Outlook, Yahoo, iCloud and the like). Mail cannot be sent in your name from a domain you do not control. |
| the domain is already registered | [`EMAIL_DOMAIN_UNAVAILABLE`](/errors/EMAIL_DOMAIN_UNAVAILABLE) | `409` | The domain is already registered, in this account or elsewhere. The answer is the same in every case, so it does not reveal who holds it. |
| the account is at its limit of domains | [`EMAIL_DOMAIN_LIMIT_REACHED`](/errors/EMAIL_DOMAIN_LIMIT_REACHED) | `409` | The account already has as many sending domains as its add-on allows. `error.details.limit` carries the number, and `max_email_domains` in the list shows it too. |
| an id that does not belong to the account | [`EMAIL_DOMAIN_NOT_FOUND`](/errors/EMAIL_DOMAIN_NOT_FOUND) | `404` | No sending domain with that id belongs to this account: it never existed here or has been removed (deleted, or expired without verification). |
| verifying or choosing a domain that is still being registered | [`EMAIL_DOMAIN_NOT_READY`](/errors/EMAIL_DOMAIN_NOT_READY) | `409` | The domain is still being registered: it has no DNS records yet (`dns_records` is empty). |

## Related

<Related>

- [Sending email](/guides/sending-email) — quotas, sandbox recipients and delivery
- [Branding and PDF](/guides/branding-and-pdf#white-label-invoices-without-beel-in-sight) — what else shows BeeL. and how to hide it
- [Register a sending domain](/emails/createAccountEmailDomain)

</Related>

---

Full OpenAPI spec: https://docs.beel.es/api/openapi