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.
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-smswithto, 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/getMMSper 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
+15551234567as 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/sendwith{"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
- 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.
- 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}
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
- Admin panel → SMS routes (or
/sms-routes): fill in the API username/password, webhook token (auto-generated if blank), default DID. - 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. - Make the webhook reachable from the internet — via Cloudflare Tunnel / Nginx Proxy Manager to
http://<pbx-ip>:8001./hooks/voipms-smsis 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.