Note de langue
Lorsqu’une traduction n’est pas disponible, le contenu anglais est affiché comme solution de repli. Conservez les codes d’erreur, les chemins d’API et les blocs de code.Fix an empty sender pool (SENDER_POOL_EMPTY)
SENDER_POOL_EMPTY is a deterministic, pre-send rejection: your request
named a valid pool, Orbit looked up the pool, and found it has no members
to draw a sender from. The send never reaches a provider, so nothing
needs a carrier-side reconciliation — add a member to the pool (or route
through a different pool) and send again.
Symptoms
- The send request returns HTTP 422 with code
SENDER_POOL_EMPTY, and the response names the pool that was empty. - The dashboard Delivery Log shows no row for the message — the send was rejected before queueing, so there is nothing to deliver.
- The pool’s detail page prompts you to add a DID, and the senders list for the pool is empty.
GET /api/v1/messaging/sender-pools/{id}/preview) when the pool has no
members, so the symptom surfaces before you send real traffic if you
preview first.
The sender-pool state machine
An empty pool sits between two healthier states, and a third failure state shares its 422 status. Tracking which state you are in tells you whether a fix has landed:
The pool preview endpoint exercises that resolution step read-only, so
you can walk the state machine without sending traffic — the empty →
resolved transition shows up there the moment a member lands.
The neighbouring codes split the failed-to-resolve state by where the
break sits, and each one has its own clear action:
SENDER_POOL_EMPTY is pool-level state (a good pointer, an empty list),
SENDER_POOL_NOT_FOUND is the pointer itself, and
NO_SENDER_CONFIGURED is organisation-level state.
Causes
The pool row exists and resolves — the failure is membership:- The pool’s
sender_didslist has no entries. Two ways a pool reaches that state: it never had members (created with an empty list and never filled, or the last member was removed), or the send carried a narrower recipient filter and none of the pool’s members were eligible, leaving zero candidates for that recipient. - A messaging service, campaign, or sender routing rule still points at the pool, so every send routed through it fails with the same code.
Pool membership mechanics
A pool member can be an E.164 DID, a numeric short code, or an alphanumeric sender ID — owned by your organisation:- E.164 DID — listed by
GET /api/v1/numbers; check the inventory before attributing the empty-pool rejection to a missing row. - Numeric short code or alphanumeric sender ID — set via the API
(
PATCH /api/v1/messaging/sender-pools/{id}withsender_dids); the dashboard’s pool editor carries them over when you edit there.
The in-flight retry semantics
SENDER_POOL_EMPTY is rejected before any pick is made, so no selection
state was written for the recipient — a retries-after-fix sequence moves
from correct-by-construction to explicitly defined:
- The idempotency key survives the pool swap. The key scopes to the request, not to the pool id inside it — when you fix membership (or route through another pool) and resubmit the same key with the same body, Orbit returns the cached 422 instead of a second send.
- The body must be byte-identical. If your fix changes the body —
for example a different
sender_pool_idvalue — the same key resubmits to409 IDEMPOTENCY_KEY_REUSED, not to a send. Generate a fresh key for the corrected request, or leave the key unset (neither the 422 nor the 409 consumes the rejection). - Nothing wrote a cache entry to clean up. Because the rejection happens before queueing, the idempotency cache holds either nothing (the plain rejection) or the 422/409 itself — both expire on the key’s 24-hour window. There is no mid-batch selection state to unwind; the idempotency and safe retries concept page covers the key-vs-body binding in full.
Gate it before launch: preview as a diagnostic
The preview endpoint rejects withSENDER_POOL_EMPTY the same way a
send does, and stays read-only — it never advances a round-robin
counter or writes a sticky assignment, so it costs nothing to run as a
pre-launch gate.
- Run
GET /api/v1/messaging/sender-pools/{id}/preview?recipient=<E.164>on a synthetic recipient after provisioning or editing a pool; expect a sender, not the 422, before you wire the pool into a campaign or messaging service. - Combine with
GET /api/v1/numbersso the pre-launch check both resolves and owns its sender before traffic flows. - When a recipient filter narrows the pool to zero candidates, preview the actual recipient you will send to — the failure mode is narrower than the empty list the pool row itself reports.
Route elsewhere: pass a sender_pool_id, or repoint the wiring
When the right move is to sidestep the empty pool rather than fix it:
- One-off divergence — pass a different
sender_pool_idon the send request itself (generate a fresh idempotency key, per the retry semantics above). Use this when only certain traffic should route away. - Wiring repoint — move the messaging service’s default pool, the campaign, or the routing rule to a non-empty pool when everything routed through the empty pool should move. Use this when the pool itself is being retired or reworked.
Client hints: branch on the confirmed channel and sender
Once a sender is in the pool, the fuller client-hint model covered on the Instagram channel page applies here too: pick the outbound sender onmetadata.channel and the source
message.from of the inbound event that started the thread, rather
than hardcoding a pool member. For a pool-backed thread the inbound
event narrows the candidates to a remembered or geo-matched pick.
Composite recovery
Example envelope
Related references
- Sender pools — create pools, add members, and understand selection strategies.
- Sender resolution — where the pool pick sits in the full precedence chain.
- Idempotency and safe retries — the key-vs-body binding semantics the retry section above applies.
- Troubleshoot sender resolution and pool errors
— the three sibling codes (
SENDER_REQUIRED,SENDER_POOL_NOT_FOUND,NO_SENDER_CONFIGURED). - FAQ: Why did my send return
422 SENDER_POOL_EMPTY? — the one-line version of this page. - Error codes — the full sender error table.