---
title: "Send an invoice by email"
method: POST
path: "/v1/companies/{company_id}/invoices/{invoice_id}/send"
tags: ["CompanyInvoiceDelivery"]
---

# Send an invoice by email

`POST /v1/companies/{company_id}/invoices/{invoice_id}/send`

## Request

- Base URL: `https://app.beel.es/api`
- URL: `https://app.beel.es/api/v1/companies/{company_id}/invoices/{invoice_id}/send`
- Auth: HTTP bearer

Sends the invoice by email, attaching its PDF by default. When no recipient is given, the
addresses configured on the customer are used.

## Headers

- `Idempotency-Key` string

## Request body

- SendEmailRequest
  - `recipients` Email[]: Recipients of the email. When omitted, the invoice's `email_config` recipients apply, then the customer's `billing_emails`, then the customer's `email`.
  - `cc` Email[]: CC recipients. Copied addresses count as recipients of the message: they are subject to the same sending restrictions and to the same quota as the addresses in `recipients`. When omitted, the CC addresses configured in the sender's email defaults apply; send an empty array to deliver the message without any copy.
  - `subject` string: Email subject (optional, if not specified uses a default)
  - `message` string: Custom message (optional, added before standard message)
  - `attach_pdf` boolean
  - `attach_source_invoices` boolean: Attach a ZIP archive (`suplidos_<invoice-number>.zip`) containing the PDFs of the source invoices referenced by the invoice's SUPLIDO consolidation lines (`source_invoice_ids`). Each PDF inside the ZIP is named `<invoice-number>_<issuer-tax-id>.pdf`. Requires `attach_pdf: true` (the ZIP accompanies the invoice PDF). Access to sources owned by managed accounts is re-checked at send time with the same rules as issuing; the request fails with an actionable error — never a partial ZIP — if the invoice has no consolidation sources (`ATTACH_SOURCE_INVOICES_NO_SOURCES`), a source is not reachable (`ATTACH_SOURCE_INVOICE_UNAVAILABLE`), a source has no generated PDF (`ATTACH_SOURCE_PDF_MISSING`), or the ZIP exceeds the size limit (`ATTACH_SOURCE_ZIP_TOO_LARGE`).
  - `language` 'es' | 'en' | 'ca': Supported languages: `es` Spanish, `en` English, `ca` Catalan.

## Response `200`

Email sent successfully

- object: The envelope every successful JSON response of the BeeL. API is wrapped in. There are no bare resources in v1 and none are planned: the payload always hangs off `data`. - A **single resource** is an object in `data`. - A **collection** hangs off a named key inside `data`, together with its `pagination`, also inside `data` — `data: {invoices: [...], pagination: {...}}`. - A collection carries `pagination` unless its operation declares itself a **closed catalogue**: a fixed, bounded list with nothing to page through. The declaration is explicit in the operation; a missing `pagination` is never something to infer. - `GET /v1/accounts` pages by cursor (`data: {accounts: [...], next_cursor}`). It is a documented variant of pagination, not another envelope. Putting the array straight into `data` with `pagination` as its sibling is the shape a v2 would adopt; v1 is not being flipped to it.
  - `success` boolean, required
  - `data` object, required: The payload. An object for a single resource; an object holding the named collection (and its `pagination`) for a listing. Never a bare array at this level in v1.
    - `email_id` string, uuid: Universally Unique Identifier (UUID v4)
    - `sent_to` Email[]
    - `sent_at` string, date-time
  - `meta` ResponseMeta
    - `timestamp` string, date-time
    - `request_id` string

## Other responses

- `202`: Accepted, not sent yet: `attach_pdf` is `true` and the invoice's PDF is not generated yet. This can happen right after issuing, whether or not the invoice goes through VeriFactu — the PDF is generated asynchronously — or, for a VeriFactu invoice specifically, while it waits for the QR of its registration. The email goes out shortly after the PDF is stored, with it attached (it can take several minutes if VeriFactu or PDF generation is delayed). Follow it with `email_id`. If the invoice ends up with no PDF (a VeriFactu registration that ends without a QR, or PDF generation that fails for good), or its PDF is not stored within a day, the email is marked as failed, with its reason. `sent_at` here is when the request was ACCEPTED, not when the email actually went out — it has not, yet. The `invoice.email.sent` webhook tells you when it goes out; a failed email has no webhook, so check its status by `email_id`.
- `400`: The invoice cannot be sent: `INVOICE_DRAFT_NOT_SENDABLE` when it is not issued yet, a draft or a scheduled invoice (issue it first), `INVOICE_NOT_REGISTERED_NO_PDF` when `attach_pdf` is `true` and the invoice has no VeriFactu registration, so it has no PDF (the reason is in `verifactu.error_message`). Other `error.code` values with this status: - `ATTACH_SOURCE_INVOICES_NO_SOURCES` — `attach_source_invoices` was requested but the invoice has no SUPLIDO consolidation lines with `source_invoice_ids`; drop the flag or add the source invoices. - `ATTACH_SOURCE_INVOICES_REQUIRES_PDF` — `attach_source_invoices: true` was sent with `attach_pdf: false`; the source-invoice ZIP only travels alongside the invoice PDF. Set `attach_pdf: true`. - `ATTACH_SOURCE_INVOICE_UNAVAILABLE` — a source invoice referenced by a SUPLIDO line no longer exists or is no longer accessible; send without `attach_source_invoices` or restore access. - `ATTACH_SOURCE_PDF_MISSING` — one or more source invoices have no generated PDF yet (their numbers are in `message`); wait for their PDFs and retry. - `ATTACH_SOURCE_ZIP_TOO_LARGE` — the ZIP of source-invoice PDFs exceeds the sendable limit (25 MB by default); send fewer sources or omit `attach_source_invoices`.
- `401`: Missing or invalid authentication. Like every other error, `message`/`detail` is localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English when the header is missing or asks for none of those.
- `403`: The company does not belong to the authenticated account, or the message is not allowed to reach one of its recipients (`ENVIO_NO_PERMITIDO`). Test environments only deliver to the account holder's own address; every address in `recipients` and in `cc` is checked. A company that is not yours to reach answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.
- `404`: The resource addressed by the path does not exist, or is not one this credential can see — the two are answered identically, so existence is never disclosed. `error.code` names the kind of resource that was missing, so a client can tell which of several ids in a path failed to resolve: - `INVOICE_NOT_FOUND` — the `{invoice_id}` (invoices, proformas and their sub-resources). - `RECURRING_NOT_FOUND` — the `{recurring_invoice_id}`. - `CLIENT_NOT_FOUND` — the `{customer_id}`. - `INVOICE_CLIENT_NOT_FOUND` — the `customer_id` referenced by an invoice body does not resolve to a customer of the issuing company. - `PRODUCT_NOT_FOUND` — the `{product_id}`. - `SERIES_NOT_FOUND` — the `{series_id}`. - `WEBHOOK_SUBSCRIPTION_NOT_FOUND`, `DELIVERY_LOG_NOT_FOUND` — the `{webhook_id}` and the `{delivery_id}`. - `PROFILE_NOT_FOUND` — the credential's account has no company profile yet: its holder has not completed onboarding, so there is no issuer to act as. - `NOT_FOUND` — the generic fallback, for the few resources that carry no code of their own. Some resources declare a more specific `404` response of their own (`CONNECTION_NOT_FOUND`, `MEMBER_NOT_FOUND`, `GRANT_NOT_FOUND`, `INVITATION_NOT_FOUND`, `REQUEST_LOG_NOT_FOUND`); it is documented on the operation. A `404` with `ENDPOINT_NOT_FOUND` is a different answer: the **path itself** does not exist in this API (a typo in the route, or a resource that was never here). It says nothing about any resource. When the path exists but not with that method, the answer is `405`, not `404`.
- `422`: `INVOICE_EMAIL_NO_RECIPIENTS`: the request sends no recipients and the recipient of the invoice has no email address.
- `429`: Rate limit exceeded. `RATE_LIMIT_EXCEEDED` when the request rate of your credential is over its limit; `TOO_MANY_REQUESTS` when an email the operation sends is held back because too many went out to that recipient in a short time. Either way, retry after the `Retry-After` seconds. `QUOTA_EXCEEDED` is the other `429`: the operation would go over a quota of your plan for the current period (issued invoices, for instance). Retrying does not help until the quota resets or the plan changes. `error.details` carries `feature_code`, `current_usage`, `quota_limit` and `reset_date`, and `RateLimit-Reset` the seconds until the reset when it is known.
- `500`: Internal server error
- `default`: Any status code the operation does not list above. Every operation declares it, so a generated client always has a branch to fall into and never loses the cause of a failure it did not anticipate. This is where the transport-level answers land — `405`, `406`, `415` and `429` — together with any status a future version of the API starts returning. All of them carry the same error envelope as the codes listed explicitly, so `error.code` is what tells them apart: switching on the status code alone is not enough. See «Transport-level errors» in the API description for when each one is produced. A `402` carrying `SUBSCRIPTION_REQUIRED` lands here when the account has no active subscription. `error.details` says why and what to do: `current_status` (absent if it never had one), `required_permission` and `action`. A `503` carrying `SERVICE_UNAVAILABLE` lands here too, with a `Retry-After` header: a dependency of BeeL itself (the database, the email provider) was momentarily unavailable and nothing was done. Retry after those seconds. A `502` carrying `EXTERNAL_SERVICE_ERROR` also lands here: an outbound integration the operation depends on failed or did not answer in time. It is a transient condition — retry with the same `Idempotency-Key` where the operation accepts one. One exception to the envelope: a failure of the network edge that never reaches the application (`502`, `503`, `504`, `524`) is generated by Cloudflare and its body is not BeeL's — it may not even be JSON. Treat those as "no answer", and retry.

---

[API](https://skmtc.dev/beel/apis/beel-public-api.md) · [All operations](https://skmtc.dev/beel/apis/beel-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/beel/apis/beel-public-api/revisions/ffbc0ae31f79?raw)
