Skip to main content

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 a 429. 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.
An explicit lane hint always wins. If you already set 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:
A bulk blast, politely shed-first:

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_at the 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.
When a marketing send is shed by its lane, the response is 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