own-pbx docs

SMS & MMS

Your voip.ms DIDs send and receive real texts. Each DID routes to an extension: inbound texts land in Zoiper, My Phone and ESP/HA devices; replies go back out from the same DID.

Need a VoIP provider?

voip.ms referral link ↗ — reasonable plans that work with this project.

How it works

Inbound — two paths, no lost messages

  • URL callback (instant): voip.ms fires a GET/POST to https://<your-panel>/hooks/voipms-sms with to, from, message, id, date. The PBX checks the token, dedupes by message ID, looks up the DID route, stores the message, and pushes it to Zoiper via SIP MESSAGE.
  • Poller (safety net, every 60s): callbacks can misfire (tunnel hiccup, timeout). A background thread polls voip.ms getSMS/getMMS per routed DID and ingests anything the webhook missed. Same dedupe table — nothing is ever delivered twice. Webhook = instant, poll = source of truth.

Outbound

  • Zoiper: text +15551234567 as a SIP MESSAGE destination. The AGI detects the external number and sends it via the voip.ms API from the DID mapped to your extension — in a background thread, so Zoiper gets its reply immediately even if voip.ms is slow.
  • My Phone → Messages: type an external number, same path.
  • ESP/HA: POST http://<pbx>:8099/messages/send with {"to": "+1555…", "from": "8801", "body": "…"} — external numbers route out through voip.ms automatically.

MMS

voip.ms callbacks carry no media, so inbound MMS is enriched via the getMMS API. Pictures arrive as [MMS: <link>] entries — tappable in Zoiper, My Phone and ESP.

Sender DID selection

Replies go out from the DID the conversation is already on (each message records its DID), so a thread stays on one number even when several DIDs route to the same extension. For a brand-new conversation: the DID whose route points at your extension, else the configured default DID, else the first route.

Setup

1. voip.ms portal

  1. Main Menu → SOAP and REST/JSON API: enable the API and set an API password (separate from your portal login; shown once, save it). The API username is your portal login email — not the account number.
  2. DID Numbers → Manage DIDs → edit each DID → Message Service (SMS/MMS): enable it, paste the webhook URL from the PBX's SMS page, choose E.164 number format, turn on callback retry, Apply.
    https://<panel>/hooks/voipms-sms?token=...&to={TO}&from={FROM}&message={MESSAGE}&id={ID}&date={TIMESTAMP}
Verify after save

The voip.ms portal sometimes mangles pasted URLs. Re-open the DID after saving and confirm the webhook URL — especially the token — survived intact.

2. PBX → SMS routes

  1. Admin panel → SMS routes (or /sms-routes): fill in the API username/password, webhook token (auto-generated if blank), default DID.
  2. Add one route per DID: DID (digits, e.g. 15551234567) → destination extension (e.g. 8801). The page shows the exact webhook URL to paste into voip.ms.
  3. Make the webhook reachable from the internet — via Cloudflare Tunnel / Nginx Proxy Manager to http://<pbx-ip>:8001. /hooks/voipms-sms is allow-listed for remote access, so it works through the tunnel even when admin pages are LAN-only.

3. Test

Text the DID from your mobile → Zoiper should pop within seconds. Reply from Zoiper → your mobile gets it from the DID. Then check the logs:

journalctl -u pbx-api -f    # webhook hits
journalctl -u pbx-brain -f  # "msg external:" lines
journalctl -u pbx-agi -f    # "msg-route external:" lines

Security notes

  • The webhook token in the URL query prevents random POSTs from storing messages.
  • The voip.ms API password is stored in the database settings, not in code.
  • Outbound still respects per-extension send switches, monthly quotas and message limits.