NewStripe: a buyer with a NIF always gets a standard invoice, and the simplified threshold is deprecated
BeeL
Get startedMulti-NIFVeriFactuRulesStripeAPI referenceChangelog

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

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.

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:

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

The rule applies to every recipient, to and cc.

In production this rule does not apply: you send to whoever the invoice is addressed to. The 403 is specific to test keys.

Send quotas

Separate from the API 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.

AxisProductionSandbox
Emails per hour6010
Emails per 24 h30030
Distinct recipient addresses per 24 h1005
Sends of the same invoice per 24 h1010

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.

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.

Need more? These are defaults, not a hard ceiling. If your billing volume needs higher limits, ask us at it@beel.es and tell us your monthly volume and your peak day — the link opens a message with the fields we need.

What you get back

StatusWhen
429Quota exhausted on some axis. There is a Retry-After — it will work later.
403Not a quota problem: the destination itself is not allowed. Waiting changes nothing.
503The 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.

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.

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 runWhat stops you
Up to 60Nothing, if you pace it across the hour
60–100The hourly cap — spread the run out
Over 100The 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 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 instead of 202. verifactu.error_message says why.
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

SourceWhat it gives you
GET /v1/accounts/{account_id}/emailsDelivery history: one record per email with status, recipients and timestamps
GET /v1/accounts/{account_id}/email-indicatorsAggregate counters for the account
The invoice's sending_historyEvery 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 webhookFires on a successful send — see 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. Only an issued invoice is sent: a draft or a scheduled invoice answers 400 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.

Reserved domains never send

Addresses on the reserved documentation domains and non-routable TLDs of RFC 2606 and RFC 6761 can never receive mail. When none of an email's recipients can, nothing is sent: /send answers 403 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.

RejectedValues
Domainsexample.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.

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; what it costs against your quota is above.

Checklist

  • In sandbox, send only to your own address — use +tag to separate test runs
  • Treat 403 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