Skip to main content
A migrated database is empty, and an empty one does not start. This is the shortest path from there to a message going out.

bootstrap

Writes the least a fresh database needs:
  • a default routing table
  • an empty MESSAGE table for a vendor to go in
  • one listener on 27777
  • one SMPP login, whose password it generates and prints once
The generated password is printed once and never again. Capture it before you close the terminal.

What it deliberately does not write

Both absences are named in its closing output, so it does not merely omit them quietly.
bootstrap refuses the moment it finds a routing table, a worker or a login, so it cannot overwrite a running deployment. There is no flag that makes it.To bring an existing file configuration in instead, use import.

Then, in this order

The order matters: each step is what makes the next one meaningful.
1

A product

A named tariff a message is priced under. Without one, pricing has nothing to key on and a product-scoped rate rule can never match.
2

A rate for that product

Both sides: what you pay a vendor (cost) and what an account pays you (price). A message matching no sell-side rule is recorded UNKNOWN, not free — delivered and billed to nobody. See rates.conf.
3

A vendor

Host, port, system id, password, and the bind counts. Nothing leaves the building until one exists. See Vendors.
4

A routing rule that names it

bootstrap leaves [MESSAGE] empty on purpose. A catch-all sending everything to your one vendor is the minimum:
5

A customer login

An account id, a product, and either an SMPP systemId/password or an HTTP pair. The account is the billing key. See credentials.yml.
6

Credit, if you are enforcing it

With smsg.balance.enabled on, an account with no balance row sends nothing. Credit enters through the database as a ledger entry — the panel writes RECHARGE rows and never the balance column directly.

Check it before you believe it

Exit 4 is the expected answer partway through this list: the deployment is sound, its own data is not — nothing to route to yet. It becomes 0 once a vendor and a rule exist.
Then send one, as a customer would:
A 200 means accepted, charged and queued — not delivered. Watch the call record for the outcome.

The two things most likely to be wrong

No sell-side rate rule matched. The call record shows the message unrated. Either the account has no rate scope, or every rule in its scope carries a product the message does not have — a product-less message is only ever shown product-less rules.
Either [MESSAGE] is still empty, or the catch-all is above your specific rules and they are unreachable, or the vendor is not bound. scripts/fireflo health distinguishes the three.