Skip to main content

Why they are separate

The separation is by purpose, not by convenience, and the reason is head-of-line blocking. A broadcast to fifty thousand contacts is a large volume of individually unimportant work. A reply to someone who just asked a question is a single piece of work that is urgent by definition — a person is watching a chat window. Put them on one queue and the broadcast is in front, and every reply waits behind it. The same argument separates flow.execution from messaging.transactional: inbound processing can be slow, because a flow may make an HTTP call or a retrieval query. Delivering the reply it produces should not queue behind the next contact’s inbound message. sync.external is isolated for a different reason: it depends on systems whose availability is not ours. When a third party is down and its jobs are retrying with backoff, that retry storm must not occupy the workers that answer contacts.
Do not mix queues by purpose. A job placed on a queue because it happens to be configured, rather than because it belongs there, removes the isolation the separation exists to provide.

Backpressure

Provider rate limits and backpressure are handled preventively, not as a reaction to a provider’s error response. A sender that discovers a limit by being rejected has already spent the request, and on a broadcast that failure multiplies across the whole fan-out — turning one rate limit into tens of thousands of failed jobs, each of which will retry.

Running the workers

Queues are processed by Horizon. An installation without it accepts messages and never answers them — see Services.