Skip to main content
A product is a tag on a login. It is matchable in the routing table, it selects the rate, and it decides whether an account’s sender IDs and content are checked. It is also optional, and the optional case is the expensive one.

An unset product is a supported state that sends for free

Leaving product off a credential is legal configuration. It matches product:isNull: in the routing table and it is priced only by rate rules that themselves carry no product. That last clause is the whole trap. A rule with no product applies to every product, but a message with no product sees only the rules that have none. They are not two spellings of “any”.
On a deployment whose rate rules all carry a product, a message with no product matches nothing. It is left unrated, and an unrated message is never charged — pricing runs before the debit, so chargeCredit skips it entirely. The message is delivered and billed to nobody.Nothing refuses it and nothing raises an error. There is one line at debug, which is the entire signal that traffic is being given away.
Two settings close it, both off by default: Least-cost routing deliberately still tolerates a vendor with no cost rate — that is a different question from whether the customer can be billed, and turning a forgotten vendor rate line into an outage would be worse than the thing it prevents.

Why the check is membership, not non-emptiness

system_type on an SMPP bind overrides the credential’s product, and it is free text validated nowhere else. So a customer who binds prem when the product is premium passes any non-empty test perfectly, and lands in exactly the unrated state above. Only checking the name against the catalogue catches it — which is what conf.product.required does, and why it is not simply “product must be set”. HTTP has no bind and therefore no override: there the credential’s product is the only source.
File configuration has no catalogue, so only the non-empty half is enforced. app_product is a database table; a gateway running on files, or one whose database is unreachable, cannot check membership at all. An unrecognisable product name is accepted, and a WARN naming the setting and the value is logged once per bind.The tolerance is deliberate — refusing every bind because a query failed is a far worse outage than the one this prevents — but it means the setting is weaker than its name on exactly the deployments that have no catalogue to check against.
Before switching either on, read the pre-flight report on the panel’s Servers page: it lists the logins that would stop connecting. Note what it cannot see — for an SMPP login it checks the credential’s fallback, so a customer binding a mistyped system_type will not appear there. The Pricing page carries the other pre-flight: the traffic of the last 24 hours that carried no price, named by account and product. That is precisely what smsg.rating.unrated.refuse would begin refusing. It counts messages whose CDR has no price_rule_id, so a rule deliberately set to 0 is not in it — a free route stays free and keeps sending.

The catalogue row

Exempting a product from whitelisting

Or Products → Edit → Check sender IDs and content. The question “must this be validated?” usually belongs to what is being sold rather than to each customer buying it — one-time passwords over a shortcode have no registered sender ID to check — and without this, every account on that product would need its own policy row saying so. Three rules surprise people:
  • An exempt product overrides the account. An account set to ENFORCE still sends freely under an exempt product.
  • No product is not exempt. Traffic naming no product at all is checked. Otherwise the way out of a whitelist would be to stop sending a system_type.
  • An unknown product is not exempt. A name with no row in app_product is checked. It is a typo in a credential far more often than an intention.
An approval filed under an exempt product protects nothing. The gateway returns OFF for that product before any merge happens, so the row is stored, visible, and never consulted. The panel counts exempt products when it decides whether switching an account to ENFORCE is safe — otherwise the guard against building an outage would wave through an account whose approvals are all inert.
Unlisting a product (enabled = false) also ends its exemption: exempt products are read as enabled AND NOT whitelist_enabled. That direction is the safe one — traffic starts being checked rather than stopping — but it can begin refusing sender IDs nobody has approved, so unlist an exempt product deliberately rather than as tidying.

Naming a product on a REST send

A REST caller may send product on /secure/send, /secure/sendbatch and /secure/rate, and it is accepted only from a login whose allowedProducts names it. An allow list rather than a plain field for the same reason as everything else on this page: the product selects the rate, so a caller free to name their own product is free to pick their own price.

How pricing works

Where each side is priced, and what an unknown amount means.

Rolling whitelisting out

The four switches, and the order they apply in.