Skip to main content

SMS

MonoPbx has an SMS core: provider configurations, numbers, a shared inbox (conversations and messages), sending with delivery tracking, signed inbound webhooks and usage counts. It is configured through the API (/sms/...); there is no SMS page in the web console yet.

:::caution Your responsibility SMS compliance (consent, opt-out, local regulations) is the customer's responsibility. Automatic handling of STOP and opt-out keywords is planned for Beta and is not active today: the hooks exist but do nothing by default. :::

Model​

provider (kind, settings, sealed credentials, webhook address)
└── number (E.164; where its conversations are routed)
└── conversation (company number + remote number)
└── message (inbound or outbound, status, segments)

Message states: queued then sent then delivered, or failed. Inbound messages are received.

Providers​

SMS providers page.

The SMS providers page.

Available provider kinds depend on the release; GET /api/v1/sms/providers lists available_kinds. On main:

  • Twilio (twilio).
  • Generic HTTP template (http_template): any HTTP SMS API configured without code. It refuses to call private, loopback or link-local addresses and plain http unless allowed.
POST /api/v1/sms/providers
{"name": "Twilio", "kind": "twilio",
"config": {"public_base_url": "https://pbx.example.com"},
"credentials": {"account_sid": "AC...", "auth_token": "..."}}

Credentials are sealed with the company's key and never returned; the API only shows auth_configured: true. Updating credentials replaces all stored ones. Routes that carry credentials ignore Idempotency-Key.

Each provider has a webhook address of the form /hooks/sms/<key> (shown as webhook_path on the provider; POST /sms/providers/{id}/webhook-address rotates it, after which the old address answers 404). Configure it at your provider as the inbound-message and delivery-report URL. For Twilio, set the number's "A message comes in" webhook to https://<your address>/hooks/sms/<key> with HTTP POST. Delivery reports for Twilio need no console setup, because the status callback is sent with each message.

The webhook address does not authenticate anything by itself. Every webhook is verified with the provider's signature (Twilio's X-Twilio-Signature, or the generic X-Mpbx-Signature HMAC) before it is parsed, and a failed check is 401 MPBX-SMS-WEBHOOK-UNAUTHORIZED. The company is taken from the provider record found by the address, never from the request.

Numbers​

SMS numbers page.

The SMS numbers page.

POST /api/v1/sms/numbers adds a number in international format (+15550100) to a provider and sets where its conversations go (route: a user, an extension, a queue or a ring group; {"kind":"none"} for nowhere). A number can be switched off with enabled: false. Providers and numbers that still have history cannot be deleted; switch them off instead.

Sending​

POST /api/v1/sms/messages {"from": "+15550100199", "to": "+15550100100", "body": "..."}

The answer is 202 with the message in state queued. from must be one of the company's enabled numbers, which also selects the provider. Texts are 1 to 1600 characters. Segments are counted the way providers bill them (160 characters per GSM-7 segment, 70 per UCS-2 segment, 153 and 67 when a message is split).

  • A permanent refusal fails the message at once with the provider's code (MPBX-SMS-PROVIDER-REJECTED if it sends none).
  • A temporary error is retried with doubling delays from 30 seconds, up to 5 attempts, then fails with MPBX-SMS-PROVIDER-UNAVAILABLE.
  • Delivery is at-least-once: in rare crash cases a message can be sent twice.

Read the inbox with GET /sms/conversations, GET /sms/conversations/{id}/messages and mark a thread read with POST /sms/conversations/{id}/read. GET /sms/usage returns daily message and segment counts per provider and direction.

Permissions​

OperationPermission
Providers and numbersintegrations:read / integrations:write
Conversations, messages, sending, usagemessaging:read / messaging:write

Both are checked company-wide. A holder who has the permission only for some departments is refused (403), because conversations have no department yet.

Events and audit​

Every state change raises an sms.* event (sms.queued, sms.sent, sms.delivered, sms.failed, sms.received) carrying ids, numbers, state and segments but never the message text. Provider and number changes are audited without credentials. See Activity, history and audit.

Error codes are listed in the repository under docs/errors/sms.md.