docs: initial SelfPhoneNG spec

Fix leftover __OP_* dialplan variables from the openphone rename (all
readers use SP_*). Record the reverse-proxy decision in 00 §10.
This commit is contained in:
iamdoubz committed 2026-10-17 14:11:09 -05:00
commit 547bb68a6f
13 files changed
+3804

No files matched your search

+139
View File
@@ -0,0 +1,139 @@
# 06 — voip.ms trunk, numbers, SMS and E911
voip.ms is the reference provider. Everything here also has a "generic" SIP
trunk fallback (same fields, no voip.ms-specific help text or API features).
> **Verify-before-trusting rule:** voip.ms changes its portal and API over time.
> Every item marked **(verify)** must be checked against the current voip.ms
> wiki/API docs or a real packet capture during M3/M9, and this file updated.
## 1. What the customer does in the voip.ms portal (shown as a checklist in the UI)
1. **Create a sub-account** (Main Menu → Sub Accounts → Create). Use one sub-account per PBX, e.g. `123456_pbx`.
- Authentication type: **User/Password** (or IP auth if the server has a static IP)
- Password: strong, random
- Device type: **Asterisk, IP PBX, Gateway or VoIP Switch** **(verify the label)**
- Allowed codecs: **G.711u (ulaw)**. Others optional.
- DTMF mode: **RFC 2833**
- NAT: **Yes**
- Encrypted SIP traffic (TLS/SRTP): optional. If on, set our trunk transport to TLS, port 5061, SRTP on.
- CallerID Number: your main DID.
- International calling: leave **disabled** unless needed (toll-fraud protection on their side too).
2. **Choose a POP server** close to the PBX (e.g. `chicago1.voip.ms`). The sub-account registers to this server.
3. **Point each DID at the sub-account** (DID Numbers → Manage DIDs → edit):
- Routing: **SIP/IAX → the sub-account**
- **POP: the same server** the sub-account registers to. A mismatch is a common cause of inbound calls failing **(verify)**.
4. **E911**: register a street address for every DID used as an emergency caller ID (DID → e911). See §6.
5. **(Optional) API access** for SMS and DID import: Main Menu → SOAP and REST/JSON API →
enable, set an API password, and **whitelist the PBX's public IP**.
6. **(Optional) SMS**: on each DID, enable SMS/MMS and set the SMS URL Callback (§5.2).
7. **Balance protection**: in voip.ms account settings, set low-balance alerts and
per-call/daily limits where offered.
## 2. Trunk form (voip.ms preset)
| Field | Default | Notes |
|---|---|---|
| Name | `voip.ms` | |
| Server | dropdown | Filled from API `getServersInfo` when API creds are set; otherwise free text with examples `chicago1.voip.ms`, `atlanta1.voip.ms`, `dallas1.voip.ms`, `newyork1.voip.ms`, `losangeles1.voip.ms`, `seattle1.voip.ms`, `montreal1.voip.ms`, `toronto1.voip.ms` **(verify names)** |
| Auth mode | registration | |
| Username | | sub-account, format `^\d+_[A-Za-z0-9_]+$` or the 6-digit main account |
| Password | | |
| Transport | UDP 5060 | TLS 5061 when encryption is enabled on the sub-account |
| Codecs | ulaw | |
| Max channels | empty | |
| API username / password | empty | The API username is the account email |
The generated PJSIP config is in `03-config-generator.md` §5.3. It follows the
voip.ms Asterisk PJSIP wiki: registration + auth + aor + endpoint + identify,
`rtp_symmetric`, `rewrite_contact`, `send_rpid` and `from_user` = account.
## 3. Inbound calls: which DID was dialed?
- voip.ms's Asterisk example matches inbound calls on `exten => <10-digit DID>`,
which means the **Request-URI user is the 10-digit DID** (no leading 1).
- The dialplan (03 §6.4) normalizes it to 11 digits and checks `sp-did-exists`. If that fails,
it falls back to the user part of the `To:` header. That covers either behavior.
- **M3 acceptance check (verify):** run `pjsip set logger on`, call the DID from a
mobile phone, and confirm which header carries the DID. Record the finding here.
- Inbound caller ID arrives as 10 digits for North American callers. The dialplan normalizes it to 11.
## 4. Outbound calls
- voip.ms accepts `1NXXNXXXXXX` (11 digits) and `011…` for international. Our routes always send 11 digits.
- Caller ID: we set `CALLERID(num)`, and `send_rpid=yes` sends it in `Remote-Party-ID`.
**(verify)** whether voip.ms honors a per-call caller ID that is one of the account's DIDs, or always
uses the sub-account's CallerID setting. If it always overrides: show a notice on the
extension "Outbound caller ID" field ("voip.ms uses the sub-account caller ID;
create one sub-account per caller ID if you need different ones") and, as a workaround, allow
several trunks (one per sub-account) with routes that choose the trunk by caller.
- Toll-free and NANP routes are created by the setup wizard (03 §6.12). International is created **disabled**.
## 5. SMS / MMS
### 5.1 Sending (voip.ms REST API)
`integrations/voipms/client.ts`:
```
GET https://voip.ms/api/v1/rest.php
?api_username=<email>&api_password=<api pw>&method=sendSMS
&did=<10-digit DID>&dst=<10-digit number>&message=<text>
```
- Response JSON `{"status":"success","sms":<id>}`. Any other `status` is an error code string. Store it in `sms_messages.error`.
- Messages over 160 characters: use `method=sendMMS` (`message` up to 2048 chars **(verify limit)**),
plus `media1`…`media3` URLs for pictures. We split nothing ourselves.
- Numbers: voip.ms wants 10 digits for NANP, so strip the leading `1`.
- The API password goes in a query string, so **never log the request URL**.
- Rate limit our sends to 1 per second per DID (queue in memory).
- Other methods used: `getDIDsInfo` (DID import), `getServersInfo` (server dropdown),
`getSMS` (optional polling fallback). All behind `voipmsApi(method, params)` with a 15 s timeout.
### 5.2 Receiving
Endpoint: `GET|POST /hooks/voipms/sms/:token`. `:token` is a 32-char random value stored
in settings key `sms.webhookToken` (shown in the DID's SMS settings with a copy button).
The full URL to paste into voip.ms is shown in the UI.
voip.ms offers two callback styles **(verify both)**:
1. **URL callback (GET)** with placeholders. The UI tells the user to paste:
`https://<host>/hooks/voipms/sms/<token>?from={FROM}&to={TO}&message={MESSAGE}&id={ID}&date={DATE}`
2. **Webhook (JSON POST)**. Accept any JSON body. Parse the known shape (look for
`data.payload.from.phone_number`, `data.payload.to[0].phone_number`, `data.payload.text`,
`data.payload.media[].url`, `data.payload.id`). If the shape doesn't match, store the raw body in
the log at `warn` level (text redacted to its length) and return 200 so voip.ms doesn't retry forever.
**M9 task:** capture one real payload into `test/fixtures/voipms/sms-webhook.json` and write the parser test from it.
Handler: normalize numbers, find the DID (404 if unknown), insert `sms_messages`
(`direction='in'`, `status='received'`), dedupe on `provider_id`, broadcast
`sms.received` to admins + `did_sms_users`, fire the webhook. Reply `ok` (text/plain, 200).
Callback IPs: voip.ms doesn't publish a fixed list **(verify)**, so security is the secret token.
Rate limit the endpoint to 60 requests/min.
## 6. Emergency calling (E911): US rules for a PBX
Legal background (summary, not legal advice; the admin guide must say "check with counsel"):
- **Kari's Law** (US, 47 CFR 9.16): users of a multi-line phone system must be able to dial
**911 directly**, with no prefix like 9, and the system must **notify** a central location
(front desk, security, admin) when 911 is dialed, where configured.
- **RAY BAUM'S Act** (47 CFR 9.16(b)): the 911 call must convey a **dispatchable
location** (street address plus room/floor where needed).
How SelfPhoneNG meets this:
1. `911` and `9911` are always dialable from every extension with no restrictions (03 §6.14). Unit test:
an extension with `outbound_class = internal`, DND on, and every trunk at max channels can still reach the 911 Dial line.
2. Notifications: email + phone ring-out + dashboard banner + webhook (04 §5.3).
3. Location: voip.ms delivers the **address registered on the DID** used as caller ID.
- Every extension can have its own `emergency_caller_id_did_id` (a DID whose e911 address matches that person's
location). Otherwise the system default `emergency.defaultCallerIdDidId` is used.
- `extensions.emergency_location` (e.g. "2nd floor, room 210") is put into the notification email. The PSAP
can't receive it through voip.ms. The admin guide must explain this limitation, and that remote
workers need a DID with their own registered address (or must call 911 from a mobile phone).
4. Warnings: the admin dashboard shows a red banner while any trunk exists but
`emergency.defaultCallerIdDidId` is unset, or while that DID's `e911_address` field is empty
("Confirm this DID has an E911 address at voip.ms").
5. **Never place a real 911 test call.** Settings → Emergency → "Test notifications" runs steps 2–4
of 04 §5.3 with the subject prefixed `TEST` and dials nothing. (Some carriers offer a test number;
check voip.ms docs before adding one.)
6. Canada: 911 works the same way through voip.ms. The admin guide links to voip.ms's Canadian e911 info.
## 7. DID import (`POST /api/trunks/:id/import-dids`)
Calls `getDIDsInfo`. Shows a list with checkboxes in the UI. Imported DIDs get
destination `{type:'hangup'}` and a warning chip "Not routed yet" until edited.