Skip to main content

The table that was never least-cost

A function name that is written and not recognised falls back to NORMAL. ->function(LRC) is a transposition anyone can make, and the table then routes first-match while its author believes it is routing by price — every vendor rate in it ignored, every message going to whichever rule happens to sit at the top.It logs routing.table.unknown-function and keeps working. The fallback is deliberate: refusing here would refuse the whole table set, and one mistyped name should not take routing down. Check the name character by character, and check the log.
There are exactly two functions, NORMAL and LCR. A table that declares no function is NORMAL.
A third function, RC, was removed in V1.28.0. It was in the enum, allowed by the database constraint, and described in the panel — and nothing anywhere branched on it, so a table set to RC routed as NORMAL while every surface an operator could consult said otherwise. Existing rows are rewritten to NORMAL, which is what they were already doing.

What changes

A NORMAL table stops at the first matching rule, which makes file order the priority. An LCR table scans every rule, treats the matches as alternatives rather than as a fallback chain, and lets only the rate in rates.conf decide.
Rule order is irrelevant inside an LCR table. Moving a rule up does not make it preferred, and putting a catch-all first does not shadow anything below it. If you are reordering rules here to change behaviour, you are changing the wrong file — the cost rates are what decide.

How a winner is chosen

1

Every rule in the table is scanned

No early exit. A rule whose target is another table is skipped with a warning — a nested cost cannot be compared, and routing at an unknown price would be worse than not routing there.
2

Dead vendors drop out

A target that is down, suspended or otherwise not accepting is skipped, so the next cheapest carries the traffic. This is the same signal a group uses; see Vendors for the four states.
3

The survivors are priced

Each remaining candidate is rated for this message’s product against the cost side of rates.conf. The cheapest wins.
4

An unrated candidate is the last resort

A vendor with no matching cost rate is kept aside and used only if nothing else matched, logging No candidate in least-cost table … has a rate.
A missing rate line degrades rather than blocks. Refusing to route an unrated message would turn a forgotten line in rates.conf into an outage; preferring it would send everything through whichever vendor was least configured. Last-resort is the only answer that is neither.It is still worth alerting on, because a vendor that is winning traffic on a missing rate is winning it at an unknown margin. See How pricing works.

Two things refused outright

Both are refused at load, whole, because neither has a partial state that routes the way anyone intended — the table set stops updating until it is fixed.
Least-cost picks by price and a group picks by policy. Expanding the members into rate candidates would silently discard the policy, and a weighted group has no meaning to give a price comparison.Use one or the other in a given table: a group where you want failover and proportional splitting, an LCR table where you want the cheapest live vendor.
Resolution is table, then group, then worker, so one of the two would be silently unreachable. Rename one of them.

Copy-and-continue has no meaning here

+copied marks a rule as “add this target and keep scanning”. An LCR table already scans the whole table and then chooses one cheapest candidate, so there is nothing for a copy to bypass. The panel says so rather than letting the flag look meaningful.

Diagnosing a choice

outSms.routing.trace, set to one serial or one account id, explains which rules matched. The selection itself — which candidate won and at what rate — is logged under outSms.routing.debug, which is only usable on an idle gateway; see How routing works. If an LCR table sends everything to one vendor, check in this order:
  • Is the function name spelled LCR?
  • Do the other candidates have a cost rate at all, for this message’s product?
  • Are the other candidates accepting? A vendor that is bound but not transmittable is skipped.
Rates are configured separately — see rates.conf and routingTable.conf.