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 (last 2 days) and ingests anything the webhook missed. Same dedupe table — nothing is ever delivered twice. Webhook = instant, poll = source of truth. On its first run the poller backfills up to 2 days of unseen messages; anything it recovers is logged asvoip.ms poll: recovered missed SMS id=….
Outbound
- Zoiper: text
+15550192837as 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.
15550192837) → 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
First check the webhook answers at all:
curl "https://<panel>/hooks/voipms-sms?token=<token>" # should return: ok
Then 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
Upgrading an existing system
Adding SMS to a system that's already running doesn't need a fresh install — the upgrade is idempotent and safe to re-run. The build ships an upgrade script that does it all: it backs up the database and /opt/pbx, applies the schema (the new tables are CREATE TABLE IF NOT EXISTS, so existing data is untouched), runs the normal deploy (new code in, services restarted, Asterisk reloaded), and verifies the services plus the webhook.
scp own-pbx-sms-voipms.zip root@<pbx-ip>:/root/
ssh root@<pbx-ip>
cd /root && unzip -q -o own-pbx-sms-voipms.zip
cd own-pbx-sms
bash scripts/upgrade-voipms-sms.sh
By hand, it's the same steps:
# 1. backup
cp -a /var/lib/pbx/pbx.db /var/backups/pbx/pbx.db.bak
cp -a /opt/pbx /opt/pbx.bak
# 2. put the new source somewhere, e.g. /root/own-pbx-sms, then
cd /root/own-pbx-sms
sqlite3 /var/lib/pbx/pbx.db < db/schema.sql
bash scripts/deploy.sh # copies code, restarts services, reloads Asterisk
# 3. verify
systemctl status pbx-api pbx-brain pbx-agi --no-pager
curl -s http://127.0.0.1:8001/hooks/voipms-sms # -> ok
No new Python dependencies were added (standard library only), so the Python environments don't change. Afterwards, finish the setup above: SMS routes in the panel, the webhook URL into each DID at voip.ms, and a test text both ways.
Rollback
cp -a /var/backups/pbx/pbx.db.<timestamp>.bak /var/lib/pbx/pbx.db
rm -rf /opt/pbx && cp -a /opt/pbx.bak.<timestamp> /opt/pbx
systemctl restart pbx-api pbx-brain pbx-agi
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.