FlaresendDocs

Errors

The error body, the eight error types, and every error code the API returns.

Flaresend uses normal HTTP status codes: 2xx for success, 4xx when the request is wrong, 5xx when something failed on the mailer's side. Every error has the same JSON body.

Error body

{
  "error": {
    "type": "validation_error",
    "code": "too_many_recipients",
    "message": "at most 50 recipients (to + cc + bcc) per email",
    "param": "to"
  }
}
FieldMeaning
typeOne of the eight types below. The HTTP status follows from it.
codeA short machine-readable name for the exact problem. Branch on this in your code.
messageA sentence for people. It can change between versions, so don't parse it.
paramThe field at fault, when there is one: from, to, attachments.0.content, headers.X-Foo, or a suppressed address. Not always present.

The response also has an X-Request-Id header. Include it when you report a problem.

Error types

TypeStatusMeaning
validation_error400The request is malformed or breaks a limit. Fix the request; retrying won't help.
authentication_error401The key is missing, wrong, revoked or expired.
permission_error403The key is valid but can't do this, for example send from a domain the project doesn't own.
not_found404The thing you asked for doesn't exist in this project, or the route doesn't exist.
conflict409The request clashes with the current state, for example canceling an email that already went out.
unprocessable422The request is well formed but can't be done, for example sending to a suppressed address.
rate_limit_error429Too many sends. Wait and retry.
internal_error500Something failed inside the mailer. Safe to retry with the same idempotency key.

Error codes

validation_error (400)

CodeWhen
missing_bodyThe route needs a JSON body and none was sent.
invalid_bodyThe body isn't valid JSON, a field is missing or has the wrong type, an address is malformed, or from is missing and the project has no default sender. param names the field.
invalid_queryA query parameter is wrong, for example limit=500 or a since without a time zone.
too_many_recipientsMore than 50 unique addresses across to, cc and bcc, or a broadcast audience larger than the broadcast cap.
invalid_headerA custom header uses a reserved name, contains a line break, or breaks a size limit.
invalid_attachmentAttachment content isn't base64, an inline attachment has no contentId, or a filename has a line break or ".
payload_too_largeThe email, attachments included, is over 5 MiB.
invalid_schedulescheduledAt isn't a date, is in the past, or is more than 30 days ahead.
invalid_templateA stored template has a syntax error, such as an unclosed {{#if}}.
invalid_template_datadata is missing a variable the template requires, or doesn't match a built-in template's schema.

authentication_error (401)

CodeWhen
missing_api_keyNo Authorization: Bearer … header.
invalid_api_keyThe key doesn't exist or isn't in the right format. Also returned for a wrong admin key.
revoked_api_keyThe key was revoked.
expired_api_keyThe key's expiry time has passed.

permission_error (403)

CodeWhen
invalid_senderfrom isn't on one of the project's allowed domains, or isn't in its allowed senders list.
project_disabledThe project is disabled.
rpc_disabledAn RPC call for a project with rpcEnabled: false.
broadcasts_disabledA broadcast call for a project that doesn't have broadcasts enabled.
admin_onlyDomain setup called with a project key. It needs the admin key.

not_found (404)

CodeWhen
route_not_foundNo route matches the method and path.
email_not_foundNo email with that ID in this project.
content_expiredThe email exists, but its body was deleted. Bodies are kept for 30 days.
template_not_foundNo stored or built-in template with that name.
template_version_not_foundThe template has no such version.
webhook_not_foundNo webhook with that ID in this project.
contact_not_foundNo contact with that ID in this project.
audience_not_foundNo audience with that ID in this project.
broadcast_not_foundNo broadcast with that ID in this project.
project_not_foundAdmin API or RPC: no project with that slug.
api_key_not_foundAdmin API: no key with that ID.
domain_not_in_projectAdmin API: the domain isn't one of the project's allowed domains.
not_foundAdmin API: the dev events route was called on a production mailer.

conflict (409)

CodeWhen
idempotency_payload_mismatchThe idempotency key was already used with a different body.
not_cancelableThe email isn't scheduled any more (so it can't be canceled or moved), or the broadcast already finished.
template_existsA stored template with that name already exists.
template_conflictSomeone else changed the template at the same moment. Reload and retry.
webhook_disabledYou tried to send a test event to a disabled webhook.
audience_existsAn audience with that name already exists.
audience_in_useThe audience has a scheduled or sending broadcast.
broadcast_not_draftOnly draft broadcasts can be edited or sent.
broadcast_activeA scheduled or sending broadcast can't be deleted. Cancel it first.
slug_takenAdmin API: a project with that slug already exists.

unprocessable (422)

CodeWhen
recipient_suppressedA recipient is on the suppression list. param is the address. See Suppressions.
cf_token_missingAdmin API: domain setup needs the mailer's CF_API_TOKEN secret.
zone_not_foundAdmin API: the token can't see a Cloudflare zone for the domain.
cloudflare_api_errorAdmin API: a Cloudflare API call during domain setup failed.

rate_limit_error (429)

CodeWhen
rate_limitedThe project sent too many emails in the last minute. The response has Retry-After: 60.
daily_limit_exceededThe project reached its daily limit. It resets at 00:00 UTC.

See Rate limits.

internal_error (500)

CodeWhen
internalAn unexpected error, or the email couldn't be put on the send queue (in which case it's marked failed and wasn't sent). The mailer logs the details with the request ID.

Errors made by the client

The @flaresend/client package throws a FlaresendError with the same type, code, message, param and status. It adds three codes of its own:

CodeTypestatusWhen
http_<status>internal_errorThe response statusThe response was an error but its body wasn't Flaresend's error JSON, for example a proxy's HTML page.
network_errorinternal_error0No response after every retry: DNS failure, connection reset, and so on.
invalid_responseinternal_errorThe response statusA 2xx response whose body isn't JSON.

Errors over RPC

Custom error properties don't survive a Cloudflare service binding, only the message does. So the mailer puts the error into the message, as FLARESEND_ERROR: followed by the error JSON, and the RPC client turns it back into a FlaresendError. Its status comes from the type, since there is no HTTP response. If you call the binding without the client, decode it yourself with decodeRpcError from @flaresend/types.

import { FlaresendError } from '@flaresend/client/rpc';

try {
  await mail.send({ to: 'ada@example.com', subject: 'Hi', text: 'Hello' });
} catch (e) {
  if (e instanceof FlaresendError && e.code === 'recipient_suppressed') {
    // e.param is the suppressed address
  }
  throw e;
}

On this page