Skip to main content

Error codes

Catalog of error.code values that appear on the BlueSender → CRM status-update channel (PUT /conversations/messages/{messageId}/status), plus the synchronous HTTP error labels and the OAuth-callback codes for completeness.

The codes split into three sources, which is worth knowing when debugging:

  • BlueSender-emitted on the status-update channel. Only CLIENT_NOT_CONNECTED (no Mac bound) and INTERNAL_ERROR (backend failure) are produced by BlueSender itself.
  • Pass-through from the Mac. Most of the codes below (IMESSAGE_*, UI_AUTOMATION_DISABLED, AX_DENIED, RATE_LIMITED, TIMEOUT, the reaction codes, and the sticker codes) are emitted by the Mac client and forwarded verbatim by BlueSender on the same channel.
  • Synchronous HTTP responses. Returned on the request the CRM made; not on the status-update channel. The HTTP-response error labels and the OAuth-callback codes below fall here.

The list is non-exhaustive — codes are free-form strings and new codes can be added without a wire-version bump. CRMs should tolerate unknown codes (see conventions).

HTTP-response error labels (synchronous, on POST /crm/webhook)​

Route-handled 4xx responses carry both a human-readable error label (stable for backwards compatibility) and a machine-readable code (SCREAMING_SNAKE_CASE, stable). Two paths short-circuit before the route handler and therefore don't emit code: unparseable JSON (rejected by Express's body parser; response body is the parser's default — typically an HTML error page rather than JSON) and 401 signature-verification failures. Both rows are flagged with — in the Code column below. Branch on code when behaviour needs to differ between sibling coded 4xx cases, fall back to HTTP status code for the uncoded cases; the error label is the primary human signal. Secondary fields (required, received, limit) provide detail.

:::tip CRM UIs typically don't render HTTP response bodies Most CRM webhook integrations only surface the HTTP status code in the conversation UI, not the JSON body. To make the specific failure visible in the message thread, BlueSender also fires an async message_status: failed ack (keyed by code) whenever the request carries a resolvable messageId — so validation errors and runtime errors now reach the CRM thread through the same channel. Build CRM-side error handling against the async ack, not the HTTP body. :::

LabelcodeHTTPMeaning
Invalid content typeINVALID_CONTENT_TYPE400The request Content-Type is not application/json.
Invalid request bodyINVALID_REQUEST_BODY400The Content-Type header passed the route's substring check for application/json but body-parser did not parse the body (e.g. application/json-patch+json, x-application/json, text/application/json), so req.body is absent. null and scalar JSON bodies (string, number, boolean) do not reach this branch — body-parser's default strict: true rejects them upstream with the malformed-JSON 400 in the row below. JSON arrays do reach the route and fall through to MISSING_REQUIRED_FIELDS rather than this row, because arrays are typeof 'object'.
(malformed JSON)—400A request whose body is not parseable as JSON is rejected by Express's body parser before the route runs. The response body in that case is body-parser's default — typically an HTML error page rather than the JSON shapes listed here. Send valid JSON.
Missing required fieldsMISSING_REQUIRED_FIELDS400One or more of locationId, contactId, phone, and message-or-attachments are missing. The body includes required and received arrays.
Message body exceeds maximum length of 20000 charactersMESSAGE_BODY_TOO_LONG400The message body length exceeds the configured cap (MESSAGE_BODY_MAX_LENGTH_CHARS, default 20,000). The body includes received (actual length) and limit.
Invalid phone number formatRECIPIENT_PHONE_INVALID400The supplied phone failed format validation.
Unsupported message type: …UNSUPPORTED_MESSAGE_TYPE400type was present on the request body but was not "SMS". BlueSender only routes through iMessage / SMS; Email (and any other value) is rejected. Omit type to use the default behavior.
(Signature error)—401Signature verification failed (when enabled).
Location not connectedLOCATION_NOT_CONNECTED404No Mac client is bound to the target locationId. The async message_status: failed ack for this case carries error.code = "CLIENT_NOT_CONNECTED" — the historical wire-contract code that Mac and CRM consumers already branch on. The HTTP-body code and the async error.code deliberately differ here; treat them as two contract surfaces.
TOO_MANY_REQUESTSTOO_MANY_REQUESTS429Per-locationId HTTP webhook rate limit exceeded — or per-source-IP when locationId is missing/malformed. See Rate limiting.

The label and code sets are not closed — new entries may be added in future backend revisions. CRMs should branch on HTTP status code first, and match the code field (or error label, for backwards compatibility) only when behaviour needs to differ between sibling 4xx cases.

Validation errors on the async message_status channel​

When POST /crm/webhook rejects a request that carried a resolvable messageId, BlueSender also dispatches PUT /conversations/messages/{messageId}/status with status: "failed" as a best-effort outbound call (fire-and-forget; it may complete before, alongside, or after the synchronous HTTP response — no ordering guarantee), so the specific failure is visible in the CRM message thread. CRM UIs that render the message thread are event-driven on this PUT and don't correlate it with the webhook response, so the lack of ordering is not user-visible — but consumers should not poll message status immediately after a 4xx and assume the failed status is already set. The async ack fires for these route-handled rows:

  • MISSING_REQUIRED_FIELDS (400)
  • MESSAGE_BODY_TOO_LONG (400)
  • RECIPIENT_PHONE_INVALID (400)
  • the 404 — with one wire-contract exception: the async ack carries error.code = "CLIENT_NOT_CONNECTED" instead of LOCATION_NOT_CONNECTED (historical wire-contract code, preserved). Treat the HTTP-body code and the async error.code as two contract surfaces for this row.

For the remaining 4xx codes, no async ack fires — only the HTTP response carries the code:

  • INVALID_CONTENT_TYPE (400) and INVALID_REQUEST_BODY (400) reject the request precisely because the body was not parsed into a JSON object (either the Content-Type was wrong, or it passed the route's substring check but body-parser declined to parse it), so there is no messageId to ack against by construction.
  • the 401 signature-verification path and the 429 rate-limit path are short-circuited by middleware before the route handler runs. The throttling middleware in particular exists to shed load, so amplifying it with an outbound CRM PUT per rejected request would defeat its purpose.

The async ack is best-effort: it also skips silently when messageId is missing or unrecoverable on an otherwise-parseable body, when OAuth is not configured on the backend, or when no OAuth token is stored for the target location. Failures of the async ack do not affect the HTTP response.

Routing (status-update channel)​

CodeMeaningRetry
CLIENT_NOT_CONNECTEDNo Mac is currently bound to the target locationId. The backend does not buffer the request — see No backend queue.After Mac reconnects.
IMESSAGE_GROUP_INVALID_RECIPIENTSThe Mac could not resolve the group recipient list to a valid iMessage thread. Common cause: a participant is not iMessage-capable on the sending Mac (mixed iMessage / SMS group).No — out-of-band fix.
IMESSAGE_ERRORGeneric iMessage send failure surfaced by the Mac's message-send pipeline. The accompanying error.message carries the underlying detail.Sometimes — depends on the underlying failure.
INVALID_FRAMEAn internal validation in the BlueSender pipeline rejected the request before the Mac could act on it.No — fix the request.

Mac-side dispatch (status-update channel)​

These codes appear when the Mac fails while handling outbound work that requires advanced features (attachments, animations, reactions, standalone stickers).

CodeMeaningRetry
UI_AUTOMATION_DISABLEDThe relevant feature is disabled at the Mac (operator config or user preference).No — out-of-band fix.
AX_DENIEDThe Mac is missing a required macOS permission.No — user action required.
RATE_LIMITEDThe Mac's outbound queue is full (cap: 500 pending jobs). The frame was rejected without enqueueing. See Rate limiting.Yes, after backoff.
INTERNAL_ERRORAn unexpected error occurred on the Mac while handling the request.Yes — typically transient.
TIMEOUTA dispatch step exceeded its internal timeout.Yes.

Reactions​

Outbound reactions ship inside the existing /crm/webhook request via a reaction block in the metadata footer — see Reactions: Outbound CRM → Mac. The codes below appear on the status-update channel.

CodeMeaningRetry
TARGET_NOT_FOUNDThe target message is not visible / resolvable on the Mac.Sometimes — see Reactions.
INVALID_KIND_FIELD_COMBOThe required sibling field for the requested reaction.kind is missing or empty (e.g., kind=classic without reaction.type, or kind=emoji without reaction.emoji).No — fix the request.
PICKER_FAILEDThe reaction picker failed to open or respond. Distinct from EMOJI_NOT_IN_RECENTS.Yes, briefly.
EMOJI_NOT_IN_RECENTSThe requested emoji couldn't be landed via either the inline Tapback Details palette (which on macOS 26 exposes only the user's recent picks) or the full system emoji picker fallback. Most emojis succeed via the system picker fallback on macOS 26+; this code surfaces edge cases where neither path lands the reaction — including the post-dispatch case where the gesture appeared to complete but the reaction wasn't observed in chat.db within ~2s (picker mis-fire). The name is legacy from before the system-picker fallback existed; treat the code itself as "couldn't deliver — fall back."No — fall back.
STICKER_NOT_FEASIBLEOutbound sticker reaction couldn't be applied. Returned when the build-time kill-switch is set or when the post-gesture chat-state probe didn't observe a new sticker reaction within the budget. (URL-based payloads are fully supported — fetch failures ack STICKER_DOWNLOAD_FAILED and non-image content acks STICKER_ENCODE_FAILED, neither of which are this code.)Yes, briefly — same shape as other dispatch failures; or fall back to a classic Tapback.
NOT_IMPLEMENTEDReserved for unknown / future kind values.No.

Stickers​

Outbound standalone-sticker sends ship inside the existing /crm/webhook request via a sticker block in the metadata footer — see Stickers: Outbound CRM → Mac. The codes below appear on the status-update channel.

CodeMeaningRetry
STICKER_DOWNLOAD_FAILEDFailed to fetch sticker.url or decode supplied bytes.Yes — typical transient network error.
STICKER_ENCODE_FAILEDFailed to re-encode supplied bytes as HEIC.No — fix the source.
COMPOSE_FAILEDThe Mac could not compose the sticker send. Includes the macOS 14 case (sticker sends require macOS 15+).Possibly.

OAuth callback (/oauth/callback/crm)​

These codes appear on the synchronous JSON body of the CRM marketplace OAuth callback. Visible only during the install / re-auth handshake, not on the regular message-routing path.

CodeHTTPMeaning
OAUTH_DISABLED503OAuth is disabled on this deployment (CRM_OAUTH_ENABLED is not true). The deployment is using PIT tokens instead.
OAUTH_NOT_CONFIGURED500OAuth is enabled but one or more required settings are missing on the backend: CRM_OAUTH_CLIENT_ID, CRM_OAUTH_CLIENT_SECRET, CRM_OAUTH_TOKEN_URL, CRM_OAUTH_ENCRYPTION_KEY, a Postgres connection string at CRM_OAUTH_DATABASE_URL (or the generic DATABASE_URL fallback), or a numeric CRM_OAUTH_REFRESH_THRESHOLD_SEC. Operator action required.
INVALID_REQUEST400The callback was hit without an authorization code (neither query string nor request body carried it).
OAUTH_EXCHANGE_FAILED400 / 500The token exchange with the CRM failed. 400 if the CRM rejected the code, 500 otherwise.

Tolerance for unknown codes​

CRMs should treat any code not in this list as transient + non- retryable — surface a generic "operation failed" indicator without aggressive retry. The contract may add new codes over time; reasonable defaults beat hardcoding the catalog.