Message priority and traffic lanes
One account often carries two very different kinds of traffic: OTP codes and fraud alerts that must leave now, and campaign blasts that can tolerate a second of shaping. Both draw on the same per-account throughput budget — so without protection, a campaign burst can consume the whole budget in a second and shed the OTP behind it with a429.
Orbit prevents that by splitting every account’s throughput into two
traffic lanes. You steer a send with the optional priority field on the
send request; the platform resolves everything else by where the send came
from. This page covers how the lanes work, how priority maps onto them, where
the reservation is exact, and how the lanes compose with the rest of the
send gates.
The two lanes
Every send meters against one shared per-account counter — a per-second budget (100 messages/second by default, adjustable on the account). The two lanes differ only in how much of that shared budget they are allowed to reach:
Both lanes increment the same counter; the lane only changes the ceiling
a send is allowed to reach. With the default 100 msg/s budget and 20%
reserve, marketing traffic tops out at 80 msg/s — the remaining 20 msg/s are
reachable only by transactional sends. A campaign blast can saturate its
own lane and get shaped; it can never consume the slice an OTP depends on.
A send with no recognizable classification resolves to the transactional
lane (full budget), so integrations that never think about lanes behave
exactly as before.
The priority send field
Pass priority on any send endpoint (POST /messages/sms,
/messages/whatsapp, and the rest of the channel surface) to steer the
message explicitly:
When
priority is "normal" or absent, the lane resolves by where the
send came from:
- Direct API / SDK sends → transactional lane.
- Campaign- or journey-origin sends → marketing lane.
metadata.traffic_lane ("transactional" or "marketing") on the request,
that value takes precedence over both priority and origin resolution — so
a campaign batch you have deliberately pinned to the transactional lane
(e.g. appointment reminders sent through the campaign tooling) keeps its
lane.
OTP sent under a campaign burst — the case the lanes exist for:
Scenario: OTP during a campaign burst
Assume the default 100 msg/s budget with a 20% transactional reserve.
Without the lanes, the 250-send burst would have swallowed the entire
budget and shed the OTPs alongside the promotions. With them, your retry
loop for campaign traffic (
wait 1 second, resubmit) never touches OTP
latency.
Priority shapes rate and queue order only. It never changes where a
message terminates, and it is not a delivery guarantee — a lane dispatch
still depends on the downstream carrier accepting the traffic.
Cluster-correct reservation (and the Redis-outage behavior)
The per-second window is enforced with an atomic counter in Redis (INCR with a 1-second expiry), shared across every API and worker replica
— so 100 msg/s means 100 msg/s cluster-wide, not per pod. Because both
lanes meter against that one shared counter, the transactional reserve is
exact on the Redis path: there is no replica-local drift that could let
a burst overrun the reserved slice.
If Redis is unreachable, the limiter deliberately falls open to a per-pod
in-memory bucket so sends never stop on an infrastructure blip. During that
window the lane ceilings still apply, but per pod rather than
cluster-wide — the reservation degrades to a best-effort lower ceiling for
marketing until Redis returns. A counter metric
(devotel.messaging.rate_limiter.fallback) records every fallback decision
so the degradation window is visible to operations.
How priority composes with the other send gates
Lane shaping is one gate in the admission chain, not a replacement for the others. A"high"-priority OTP still walks every gate you enabled — the
lane only decides how much of your throughput budget the send may
claim:
- Opt-outs and suppression lists still refuse the recipient first — no priority ships a message to someone who opted out.
- Frequency caps and quiet hours still apply — a high-priority send
outside your quiet-hours window is blocked and reports
next_allowed_atthe same as any other send. - Throughput ceilings (per-service and per-number caps) still meter independently of the tenant budget — they stack on top of the lanes.
- Billing flags still pause the whole account regardless of lane.
429 RATE_LIMIT_EXCEEDED with Retry-After: 1 and a traffic_lane: "marketing"
detail — the same wait-one-second-and-retry posture as every other
throughput ceiling. See the
rate-limit and cooldown taxonomy
for the full map of limiter families and their error codes, and
send gating and quiet hours for
the admission order a send walks before dispatch.
See also
- Outbound send gating — the full gate chain and what each refusal looks like
- Rate-limit and cooldown taxonomy — every limiter family and its error code
- Voice & messaging pricing and throughput — per-service and per-number throughput ceilings
- Rate limits — per-endpoint API request limits and retry patterns