> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Notify cascade cost model: per-hop price bounds and the cumulative waterfall cap

> The two independent cost bounds a notify waterfall honors: a per-step max_price that rejects a pricey hop before billing, and a cumulative max_total_price that trims the whole escalation tail against a declared budget.

# Notify cascade cost model

A notify waterfall chases one recipient across a chain of channels, one hop at a time. [Cascade failover policy](/concepts/message-cascade-groups) covers how the chain is shaped and read back; this page anchors the two cost bounds that govern how much that chase is allowed to spend. Both bounds are tenant-owned controls you set per request — Orbit enforces them, it never overrides your declared budget.

## The per-recipient waterfall

`POST /api/v1/notify` with `mode: "waterfall"` encodes one recipient's channel hops in preference order — for example `rcs → whatsapp → sms` — and tries them in that order. The first binding fires immediately; the rest ride along as an escalation tail that advances only when the active hop reports undelivered. The [notify composer guide](/guides/notify-cascade-failover) covers building the chain and bounding the escalation window; the page above covers how groups and legs resolve. Two cost bounds layer on top of that ordering.

## Bound 1: per-hop `max_price`

Every binding accepts its own `max_price`, and the request-level `max_price` applies to any binding that omits one (per-binding wins over shared — the same merge rule as body, sender, and template fields). The cap is per-step, not per-cascade: it caps each hop independently of every other hop.

Before a hop is billed, the send pipeline resolves that hop's projected total price — for SMS and MMS that means the per-segment rate multiplied by the segment count of the body; every other channel prices per message. If the projected total exceeds the cap, the hop is rejected before the wallet is touched, and the waterfall moves on to the next binding instead of firing an expensive channel even once. A binding that passes its own cap never burns more than the cap declared for it, regardless of what the other hops cost.

Two consequences of the per-step semantics:

* **A pricey hop is skipped, not absorbed.** An `rcs → whatsapp → sms` chain where RCS prices over its bound simply skips RCS — the SMS safety net still fires.
* **It does not bound the cumulative spend.** A chain can pass the per-hop check on every single leg and still spend far more than you intended chasing one recipient. That is what the cumulative cap exists to bound — see below.

## Bound 2: the cumulative `max_total_price`

`max_total_price` is a cumulative budget across the entire waterfall: the primary send plus every armed fallback hop, in USD. The two bounds answer different questions — `max_price` says *no single hop may exceed this*, `max_total_price` says *chasing this recipient may not exceed this in total*.

When you declare a cumulative cap, the resolved per-channel cost estimates are summed in your declared order along the tail, and a hop is dropped from the tail the moment including it would push the running total over the cap. The hop order itself is never reordered by price — only trimmed — so a richest-first chain stays richest-first; the `voice` leg of a `rcs → whatsapp → voice → email` chain is the hop that comes out, not re-sequenced. A dropped hop costs no spend, and a cheaper hop later in the chain is still kept even when an earlier, pricier hop was dropped — the running total only grows on kept hops. A channel that resolves with no published rate contributes zero to the running total: the cumulative cap only constrains priced channels.

If cost estimation itself fails, the full chain is kept uncapped rather than silently truncated.

### `max_price` vs `max_total_price`

|                     | `max_price` (shared or per-binding) | `max_total_price`                   |
| ------------------- | ----------------------------------- | ----------------------------------- |
| Scope               | Each hop, independently             | The whole cascade, summed           |
| Enforced when       | Pre-send, on every hop attempt      | At arm time, when the tail is built |
| Effect              | Hop rejected, waterfall advances    | Hop dropped from the tail entirely  |
| Applies in `fanout` | Yes — caps every fan-out send       | No — fan-out has no cascade to cap  |

You can set both on the same request: the per-hop bound screens every attempt, and the cumulative bound trims the tail. In `fanout` mode only `max_price` applies — fan-out fires every binding at once, so a cumulative trim has nothing to act on.

## What to branch on

The waterfall response reports hop-level outcomes you can branch on:

* `active` — the binding that queued, or `null` when every binding rejected synchronously (the response is 422 in that case).
* `skipped` — bindings rejected synchronously before `active` queued, each with a machine-readable error code.
* `fallback_chain` — the ordered tail still pending after `active`.
* `max_total_price` — the declared cumulative cap, or `null` when you did not set one.
* `dropped_for_cost_cap` — channels dropped by the cumulative cap, each with its channel name, so the response never advertises a fallback that will never fire. Empty when no cap was set.

Branch per binding — for example, surface a skip on a pricey hop separately from the cumulative-cap drops — and never blanket-retry the batch on a partial failure.

## Position vs alternatives

The cascade cost model complements the fallback planes in [Cross-channel fallback](/concepts/cross-channel-fallback). The org-level cross-channel chain answers *can this channel reach this recipient* on reachability failures; the notify waterfall is a per-request preference chain where price bounds are declared because the ordering is an explicit cost-vs-reach trade. Inside the waterfall, `max_total_price` does for a notify cascade what the campaign-batch budget fallback does for campaign sends: bound a whole escalation against a declared number instead of relying on each hop to screen itself.

Order the chain richest-first (RCS → WhatsApp → SMS, per [Cross-channel fallback](/concepts/cross-channel-fallback)), declare a per-hop bound on every hop you are not willing to overpay for, and declare a cumulative cap whenever a bounded per-recipient budget matters more than maximal channel reach.

## Related

* [Cascade fallback chains in the notify composer](/guides/notify-cascade-failover) — build the per-recipient chain and bound the escalation window.
* [Cascade failover policy and groups](/concepts/message-cascade-groups) — one logical message ID across every hop.
* [Cross-channel fallback](/concepts/cross-channel-fallback) — the org-level capability chain the ordering suggestion above references.
* [Pricing and rate resolution](/concepts/pricing-rate-resolution) — how the per-hop projected price is resolved.
