# Sending email

How BeeL. delivers invoice emails — the sandbox recipient restriction, per-account send quotas, queued delivery, and bulk limits.

Sending an invoice by email is `POST /v1/companies/{company_id}/invoices/{invoice_id}/send`.
Two things about it are not obvious from the endpoint reference, and both will bite an
integration: **the sandbox only lets you write to yourself**, and **there are send quotas
that are separate from the API rate limits**.

## Sandbox only sends to your own address

<Callout type="warn">
  With a `beel_sk_test_` key you can only send to the **account holder's own address**.
  Any other recipient is refused with `403` [`ENVIO_NO_PERMITIDO`](/errors/ENVIO_NO_PERMITIDO).
</Callout>

**Plus-addressing is fully supported**, and it is the intended way to separate test
sends. The comparison strips the `+tag` before matching, so all of these count as *your*
address:

```text
you@yourdomain.com
you+test1@yourdomain.com
you+customer-fixture@yourdomain.com
```

The rule applies to **every** recipient, `to` **and** `cc`.

<Callout type="info">
  In production this rule does not apply: you send to whoever the invoice is addressed to.
  The `403` is specific to test keys.
</Callout>

## Send quotas

Separate from the [API rate limits](/guides/rate-limits), which count *HTTP requests*,
there are quotas on **emails actually sent**, per account. Note the unit: one bulk call
with 40 recipients is one request but forty emails.

| Axis | Production | Sandbox |
|---|---|---|
| Emails per hour | 60 | 10 |
| Emails per 24 h | 300 | 30 |
| Distinct recipient addresses per 24 h | 100 | 5 |
| Sends of the *same* invoice per 24 h | 10 | 10 |

<Callout type="warn">
  **The windows start at your first send, not on the clock.** Each one opens with your
  *first* send and lasts 24 hours (or one hour) from there — there is no reset at midnight.
  Send at 23:00 and resume at 09:00 and you are still inside the same window.
</Callout>

The distinct-recipients axis counts **how many different addresses** you wrote to, not how
many emails you sent: ten invoices to the same customer count as one address. On this axis
a `+tag` makes a different address: `you+test1@…` and `you+test2@…` are two of the sandbox's
5. Each environment counts its own addresses: those you
write to in the sandbox do not use up the production allowance, nor the other way round.

**Every email your account sends counts, not just invoices.** Account verification,
password resets, invitations and welcome messages come out of the same allowance, because
they are attributed to your account too. Only mail with no account behind it at all is
exempt. In practice this is a handful a month, but it is the same bucket.

Spam complaints reduce these ceilings — each complaint in the last 30 days halves them,
and at 3 the account is suspended from sending. Ordinary invoicing never approaches
this.

<Callout type="info">
  **Need more?** These are defaults, not a hard ceiling. If your billing volume needs
  higher limits, ask us at [it@beel.es](mailto:it@beel.es?subject=Higher%20email%20sending%20limits&body=Hi%20BeeL.%20team%2C%0A%0AWe%20need%20higher%20email%20sending%20limits%20for%20our%20account.%0A%0A-%20Account%20%2F%20NIF%3A%20%0A-%20Environment%3A%20production%0A-%20Invoices%20we%20send%20per%20month%3A%20%0A-%20Peak%20day%20%28e.g.%20month-end%20run%29%3A%20%20invoices%20to%20%20different%20customers%0A-%20Limit%20we%20are%20hitting%3A%20%0A%0AThanks%2C%0A)
  and tell us your monthly volume and your peak day — the link opens a message with the
  fields we need.
</Callout>

### What you get back

| Status | When |
|---|---|
| `429` | Quota exhausted on some axis. There is a `Retry-After` — it will work later. |
| `403` | Not a quota problem: the destination itself is not allowed. Waiting changes nothing. |
| `503` | The quota could not be checked right now. There is a `Retry-After`; the same send works once it passes. |

The response does not name which axis you hit or how much is left, so program against the
status code rather than trying to read a reason out of the body.

### Where the rejection reaches you

Both checks run **synchronously** when *you* ask for the send — `/send` and
`/deliveries`. There you get the `429` or `403` as the response, and nothing was queued.

<Callout type="warn">
  **An auto-sent invoice is different.** When you create an invoice with
  `send_automatically`, the creation succeeds and returns `201`; the email is authorised
  later, when it is delivered. So a quota or destination rejection **does not** reach you
  as an error on that call — it lands in the delivery history as a failed send. If you
  rely on automatic sending, read the outcome there rather than from the creation
  response.
</Callout>

## Sending a whole month at once

Plenty of integrations bill on the last day of the month and send everything in one run.
That is the run the quotas bind on first, so it is worth pacing rather than firing in a
loop.

**Work out which limit you hit first.** For a run of *N* invoices to *N* different
customers, in production:

| Customers in the run | What stops you |
|---|---|
| Up to 60 | Nothing, if you pace it across the hour |
| 60–100 | The hourly cap — spread the run out |
| Over 100 | The distinct-recipients cap — the run does not fit in one window |

The 300-email cap is rarely the binding one. **Distinct recipients (100 per 24 h) is**,
because a month-end run is by definition one email each to many different people.

### How to run it

1. **Pace the run evenly across the hour.** Staying under 60 emails an hour means you never
   see a `429`.
2. **Handle the `429` as a pause, not a failure.** It carries `Retry-After` in seconds —
   sleep that long and continue from the same invoice. Nothing was sent, so there is
   nothing to undo.
3. **Split a run of more than 100 customers, and mind where the window starts.** The
   24 hours run from your first send, not from midnight: if you send 100 at 23:00, you are
   not clear again until 23:00 the next day, not at 00:00. Plan the batches from your own
   start time. Issuing the invoices is not limited — only sending them is, so you can
   create all 250 on the last day and drain the sending queue over the following days.
   The `429` carries `Retry-After` with the exact seconds left, so the safest loop is to
   honour it rather than compute the window yourself.
4. **Ask for more instead of engineering around it** if this is your normal shape. Tell us
   your peak day using the link above and we will raise the limits.

### Where bulk delivery helps, and where it does not

`POST /v1/companies/{company_id}/invoices/deliveries` puts many invoices in **one** email,
so it costs one email against your quota no matter how many invoices it carries.

That helps when several invoices go to the **same** recipient — a customer with twelve
monthly invoices, or a copy of everything to your own accountant. It does **not** help a
month-end run to many different customers: each customer still needs their own email, so
you still spend one email and one distinct recipient per customer.

## Delivery itself is asynchronous

Once the send is authorised, the actual delivery is queued. A `2xx` therefore means
**accepted for delivery**, not delivered:

- `sent_at` in the response is when BeeL. queued the email. On the invoice resource,
  `sent_at` is when the provider *accepted* the message — still not when it reached the
  mailbox.
- `email_id` in the `/send` response is not the provider's message id.
- A send can still fail after the `2xx` — a bounce or a provider outage surfaces later.

### Sending before the PDF exists

The PDF of an issued invoice is generated after issuing, and under VeriFactu only once the QR of
its registration exists. A `/send` with `attach_pdf: true` (the default) that arrives before
then is not refused: it answers **`202`**. The email goes out as soon as the PDF is stored, with
it attached, the same way as the automatic email.

- **`sent_at` in the `202`** is when the request was accepted, not when the email went out: it
  has not gone out yet. Follow it with `email_id` in the [delivery history](#observing-what-actually-happened)
  or its webhook for the real send time.
- **If the invoice never gets a PDF** — a VeriFactu registration that ends without a QR — the
  email is recorded as failed, with its reason. It is not sent without the PDF.
- **An invoice that is already known to have no PDF** — under VeriFactu and not registered with
  the AEAT — answers `400` [`INVOICE_NOT_REGISTERED_NO_PDF`](/errors/INVOICE_NOT_REGISTERED_NO_PDF) instead of `202`.
  `verifactu.error_message` says why.

```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices/{invoice_id}/send" \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "recipients": ["you+test@yourcompany.com"] }'
# → 202 while the PDF is being generated; 200 once it exists
```

### Observing what actually happened

| Source | What it gives you |
|---|---|
| `GET /v1/accounts/{account_id}/emails` | Delivery history: one record per email with `status`, recipients and timestamps |
| `GET /v1/accounts/{account_id}/email-indicators` | Aggregate counters for the account |
| The invoice's `sending_history` | Every send of that invoice — sending it again appends, it never overwrites. Only single-invoice responses carry it; the list endpoint omits it. A failed or rejected send carries `error`: a short explanation in the language of the request, for a person to read — branch on `status`, never on its text |
| The `invoice.email.sent` webhook | Fires on a successful send — see [Events](/webhooks/events) |

Statuses are `QUEUED`, `REJECTED`, `SENT`, `FAILED`, `DELIVERED`, `BOUNCED` and `OPENED`. A
message stopped by policy is recorded as `REJECTED` and never sent — it is listed rather
than omitted, and it is terminal, so nothing retries it.

### Retries

A failed delivery is retried with exponential backoff and jitter, up to **5 attempts**,
and then given up on — nothing retries it for you after that, so the delivery record is
the only place the outcome lives. Failures that cannot improve on a retry (an invalid
recipient, a rejected destination) are given up on the first attempt instead of burning
the whole budget.

## Recipients

The recipient list is resolved in this order, first match wins:

1. `recipients` in the `/send` body
2. The invoice's own `email_config`
3. The customer's `billing_emails`
4. The customer's `email`

`billing_emails` are the addresses the customer wants its invoices at — accounts payable, a
shared invoices inbox — so they take precedence over `email`, which is the general contact
address and only receives the invoice when no billing address is set. An invoice generated by
a recurring invoice resolves the same way, with the template's
`email_configuration.recipients` in the place of the invoice's `email_config`.

The same order applies to the manual `/send`, to the automatic send when an invoice is issued with `send_automatically: true`, and to recurring invoices. An invoice always keeps its customer — duplicating it, correcting it, converting a proforma or creating a recurring invoice from it all preserve `customer_id` — so a **corrective issued with `send_automatically: true`** from an invoice that has a customer uses the customer's addresses too, with no ad-hoc recipient needed.

If nothing resolves, the call fails with `422` [`INVOICE_EMAIL_NO_RECIPIENTS`](/errors/INVOICE_EMAIL_NO_RECIPIENTS).
Only an issued invoice is sent: a draft or a scheduled invoice answers
`400` [`INVOICE_DRAFT_NOT_SENDABLE`](/errors/INVOICE_DRAFT_NOT_SENDABLE) — issue it first. An invoice whose PDF does not exist yet is accepted with `202`, see
[Sending before the PDF exists](#sending-before-the-pdf-exists).

### Reserved domains never send

Addresses on the reserved documentation domains and non-routable TLDs of
[RFC 2606](https://www.rfc-editor.org/rfc/rfc2606) and
[RFC 6761](https://www.rfc-editor.org/rfc/rfc6761) can never receive mail. When **none** of
an email's recipients can, nothing is sent: `/send` answers `403` [`ENVIO_NO_PERMITIDO`](/errors/ENVIO_NO_PERMITIDO), and an
automatic send is recorded as `REJECTED`. When at least one recipient is real, the email goes
out and the reserved addresses simply bounce.

| Rejected | Values |
|---|---|
| Domains | `example.com`, `example.net`, `example.org` (and any subdomain) |
| TLDs | `.test`, `.example`, `.invalid`, `.localhost`, `.local` |

A fixture customer whose only address is `cliente@example.com` never receives anything.
Use a `+tag` on your own address instead — which is what the sandbox requires anyway.

> **Rules that apply here:** [CNT-018 · Invoices go by email only with the recipient's consent](/rules/contents#cnt-018)

## Bulk sending

`POST /v1/companies/{company_id}/invoices/deliveries` sends the PDFs of several invoices
as **one email to the recipients you name** — never one email per customer. Its limits,
attachments and per-invoice failures are in
[Bulk operations and exports](/guides/bulk-operations-and-exports#deliveries-many-invoices-in-one-email);
what it costs against your quota is [above](#where-bulk-delivery-helps-and-where-it-does-not).

## Checklist

- In sandbox, send only to your own address — use `+tag` to separate test runs
- Treat `403` [`ENVIO_NO_PERMITIDO`](/errors/ENVIO_NO_PERMITIDO) as "change the recipient", never as "retry"
- Treat `429` on `/send` as a send quota, not an API rate limit
- Treat a `2xx` as "queued", and read the delivery history for the real outcome
- Treat a `202` from `/send` as "will go out when the PDF is stored", not as sent
- Never put `example.com` or a `.test` address in fixtures
- Pace month-end runs under the hourly cap, and split them if they exceed 100 customers
- Remember the windows roll from your first send, not from midnight
- Treat an auto-sent invoice's `201` as "created", not as "email accepted"
- Use bulk delivery when several invoices share a recipient — not as a way around the caps
- Write to it@beel.es if your volume needs higher limits

> **Rules that apply here:** [DAT-008 · Deliver the invoice to the customer in time](/rules/dates#dat-008)

## Related

<Related>

- [Branding and PDFs](/guides/branding-and-pdf) — what the attached invoice looks like
- [Bulk operations and exports](/guides/bulk-operations-and-exports) — send many invoices at once
- [Webhook events](/webhooks/events) — hear back when an email goes out

</Related>

---

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