Confirm inbound SMS→AMI working live; rule out outbound over SIP
Inbound: a real text through the full path (Anveo webhook -> relay -> AMI MessageSend -> Sipnetic) landed with a SIP 200 OK, confirmed live. Removes the last "UNVERIFIED"/"believed correct" hedges from sms-inbound.sh now that the AMI permission class and the Destination-not-To fix are both proven, not just plausible. Outbound over SIP: tested directly by sending a MESSAGE toward Anveo's trunk (the mirror image of the inbound webhook). Anveo's SBC responded 501 Not Implemented -- a real, unambiguous rejection of the method itself. Closes off this avenue for good, symmetric with inbound SMS-over-SIP already being confirmed unavailable on this DID: neither direction is offered on this account via SIP. Sending still has no working path here until Anveo activates HTTP SMS-API access. Also updates docs/anveo-direct-setup-guide.md's SMS section, which still described the ntfy-based mechanism this session fully replaced with AMI/Sipnetic delivery, and corrects its stale "not available on Direct" sending claim with what's actually been confirmed: SIP MESSAGE outbound is a dead end (501), the HTTP API is real but pending Anveo activation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JDyKC6Kdg7tofmYSmRtgww
This commit is contained in:
@@ -199,7 +199,7 @@ In the Security Dashboard's PSTN Trunk tab:
|
||||
# container is named easy-asterisk-do instead
|
||||
```
|
||||
|
||||
## 8. SMS — receiving verification codes
|
||||
## 8. SMS — receiving texts in Sipnetic
|
||||
|
||||
Voice and SMS are separate features on an Anveo DID and are configured in
|
||||
different places. This section covers **receiving** only; see "What about
|
||||
@@ -242,41 +242,49 @@ Run the installer and follow what it prints:
|
||||
sudo ./setup.sh sms-inbound
|
||||
```
|
||||
|
||||
It generates a long random ntfy topic, then offers two ways for Anveo to
|
||||
reach it:
|
||||
A small systemd service on the droplet receives Anveo's webhook request,
|
||||
looks up which extension (or Ring Group) currently owns the destination DID
|
||||
— the exact same `pstn-personal-dids.conf`/`pstn-groups.conf` data
|
||||
inbound-voice ring logic already reads — and delivers the text into
|
||||
Asterisk over its Manager Interface as a SIP `MESSAGE`, landing in Sipnetic
|
||||
the same way this project's internal extension-to-extension texting already
|
||||
does. Not a push notification: a real message in the softphone's own thread.
|
||||
|
||||
- **Relay (recommended)** — a small systemd service on the droplet receives
|
||||
Anveo's request and republishes to ntfy properly. Two concrete wins: a
|
||||
message body containing `&` survives intact (Anveo interpolates the text
|
||||
into the query string unescaped, so an unencoded `&` otherwise truncates
|
||||
the message), and your ntfy credentials never get stored in Anveo's portal.
|
||||
- **Direct** — Anveo calls ntfy itself; nothing runs on the droplet. Simpler,
|
||||
but the URL you paste into Anveo carries your ntfy token, and the `&` case
|
||||
loses the tail of the message.
|
||||
("SMS over SIP" — Anveo delivering the text as a MESSAGE/INVITE directly to
|
||||
this box's SIP trunk, skipping the HTTP webhook entirely — was tried first
|
||||
and confirmed **not offered** on this DID: the SMS tab only ever showed the
|
||||
plain "Forward to URL" option, no SIP-based alternative.)
|
||||
|
||||
Then in the Anveo portal: **Phone Numbers → the DID → SMS tab**. The tab has
|
||||
exactly one control — a **Forward to URL** checkbox and a text field. Tick the
|
||||
box, paste the string the installer printed into the field, and press **SAVE**
|
||||
(not RETURN, which discards). Keep the `$[message]$` placeholder **last** in
|
||||
that URL — that's what makes the unescaped-`&` case recoverable.
|
||||
that URL — Anveo interpolates the message text into the query string
|
||||
unescaped, so a body containing `&` otherwise truncates; with the placeholder
|
||||
last, everything after it is recoverable verbatim.
|
||||
|
||||
The field has no visible length limit, but the generated URLs are long
|
||||
(~90 characters in relay mode, ~150+ in direct mode, since that one carries
|
||||
the ntfy auth parameter). After saving, reopen the tab and confirm the whole
|
||||
string came back intact rather than truncated — if it didn't, relay mode is
|
||||
the shorter of the two.
|
||||
|
||||
Send a text to the number from another phone; the notification should arrive
|
||||
within seconds. `journalctl -u sms-inbound -f` shows sender, recipient and
|
||||
message length (never the body — these are one-time passcodes and the journal
|
||||
has a wider audience than the notification does).
|
||||
Send a text to the number from another phone; it should land in Sipnetic
|
||||
within seconds. `journalctl -u sms-inbound -f` shows sender, recipient,
|
||||
message length, and the full AMI exchange — never the message body itself,
|
||||
since the journal has a wider audience than the message thread does.
|
||||
|
||||
### What about sending?
|
||||
|
||||
Not covered, on purpose. Outbound SMS isn't available on Anveo Direct — Anveo
|
||||
support directs users to an Anveo **Retail** account for it, which is a
|
||||
second account to fund and manage. Any of the free texting apps covers
|
||||
sending without involving this box.
|
||||
Two things confirmed this way, neither of them a working path yet:
|
||||
|
||||
- **Outbound over SIP** — sending a `MESSAGE` toward Anveo's trunk (the
|
||||
mirror image of the inbound webhook) was tried directly: Asterisk placed
|
||||
the request fine, but Anveo's SBC came back `501 Not Implemented`. A real,
|
||||
unambiguous rejection of the method itself — not a config or auth problem,
|
||||
and not something any amount of retrying the request format will get past.
|
||||
- **Outbound via Anveo's HTTP API** (`api/v1.asp?action=sms`) — real and
|
||||
documented, but gated behind Anveo support activating SMS-API access for
|
||||
this account; still pending as of this writing (limited support tickets on
|
||||
this account, so this has been a slow back-and-forth rather than a quick
|
||||
toggle).
|
||||
|
||||
Until that activation lands, there's no outbound path from this box. Any of
|
||||
the free texting apps covers sending in the meantime without involving it.
|
||||
|
||||
### MMS and group texts
|
||||
|
||||
|
||||
+21
-20
@@ -60,11 +60,8 @@ _sms_detect_container_name() {
|
||||
# ── AMI: a scoped manager.conf user, MessageSend only ───────────────────────
|
||||
# Localhost-only (bindaddr + permit below) since the relay runs on this same
|
||||
# host, not over the network — no firewall port to open for this. "message"
|
||||
# is the AMI permission class MessageSend needs; this hasn't been confirmed
|
||||
# against a live MessageSend call yet (see the relay's own comment on
|
||||
# ami_deliver) — if the very first real delivery attempt gets an AMI
|
||||
# permission error in the journal, widen read/write here first before
|
||||
# looking anywhere else.
|
||||
# as the AMI permission class is confirmed live: MessageSend succeeds with
|
||||
# just read/write = message, no wider class needed.
|
||||
#
|
||||
# Idempotent: creates manager.conf fresh if absent, otherwise ensures
|
||||
# enabled=yes and the [smsrelay] section exist without disturbing anything
|
||||
@@ -333,21 +330,25 @@ def _ami_read_response(sock_file):
|
||||
def ami_deliver(from_number, to_exts, body):
|
||||
"""Logs into AMI once and sends one MessageSend action per recipient.
|
||||
|
||||
"message" as the AMI permission class is confirmed live (login succeeds).
|
||||
Destination (not To) is what actually resolves an outgoing message's
|
||||
endpoint/technology -- confirmed against this box's own
|
||||
`manager show command MessageSend`: To alone is documented as a
|
||||
backward-compatible fallback for the destination, but live testing
|
||||
(bare "pjsip:212", then "pjsip:212@domain", both for To with no
|
||||
Destination) produced zero SIP wire traffic in either case -- `pjsip
|
||||
set logger on` during a real attempt showed Asterisk never even tried
|
||||
reaching the target's registered contact, so that fallback path isn't
|
||||
actually wired up on this Asterisk version regardless of what the docs
|
||||
promise. Destination's documented "endpoint" form -- bare "pjsip:<ext>",
|
||||
no domain -- resolves via the endpoint's own default aor/contact, which
|
||||
is exactly the live, registered contact `pjsip show contacts` already
|
||||
confirmed exists. Every AMI response is still logged in full so the
|
||||
next attempt is self-diagnosing if this isn't the whole fix either.
|
||||
Confirmed working end-to-end live (2026-07-27): a real inbound text
|
||||
delivered through this exact path landed in Sipnetic with a SIP 200 OK.
|
||||
Two things worth keeping straight if this ever needs touching again:
|
||||
|
||||
- "message" as the AMI permission class is enough on its own (login +
|
||||
MessageSend both succeed with just read/write = message).
|
||||
- Use Destination, not To, to resolve the endpoint. `manager show
|
||||
command MessageSend` documents To-alone as a backward-compatible
|
||||
fallback for the destination when Destination is omitted, but live
|
||||
testing that fallback (bare "pjsip:212", then "pjsip:212@domain",
|
||||
both as To with no Destination) produced zero SIP wire traffic
|
||||
either way -- Asterisk never even attempted to reach the target's
|
||||
registered contact. Destination's documented "endpoint" form (bare
|
||||
"pjsip:<ext>", no domain) is what actually resolves via the
|
||||
endpoint's default aor/contact and is what's used below.
|
||||
|
||||
Every AMI response is still logged in full, since a wrong recipient,
|
||||
an unregistered contact, etc. all still need to be diagnosable from
|
||||
the journal alone.
|
||||
|
||||
Returns (delivered_count, total_count)."""
|
||||
if not AMI_SECRET:
|
||||
|
||||
Reference in New Issue
Block a user