No description
  • Go 98.9%
  • Shell 0.6%
  • Dockerfile 0.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
George Craggs bf2a7c839f
All checks were successful
CI / check (push) Successful in 45s
CI / image (push) Successful in 11m5s
Read phone numbers from encrypted Proton cards
The contact list only includes names. Phone numbers are on the encrypted card, and a failed signature was discarding that card.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-24 01:22:09 +01:00
.forgejo/workflows Publish amd64 and arm64 images 2026-10-23 16:58:10 +01:00
cmd/mautrix-aasms Enable end-to-bridge encryption for Beeper 2026-10-23 22:08:25 +01:00
pkg Read phone numbers from encrypted Proton cards 2026-10-24 01:22:09 +01:00
.gitignore aa.net.uk matrix connector 2026-09-25 11:07:52 +01:00
build.sh aa.net.uk matrix connector 2026-09-25 11:07:52 +01:00
Dockerfile Fix reachable govulncheck findings 2026-10-23 17:10:22 +01:00
example-config.yaml Harden the AAISP inbound webhook 2026-10-24 00:12:44 +01:00
go.mod Name SMS chats from Proton Contacts 2026-10-24 00:03:07 +01:00
go.sum Name SMS chats from Proton Contacts 2026-10-24 00:03:07 +01:00
README.md Finish Proton human verification in contacts-login 2026-10-24 00:26:42 +01:00

mautrix-aasms

A Matrix / Beeper puppeting bridge for AAISP SMS, built on mautrix-go bridgev2.

Send and receive plain-text SMS through one or more AA VoIP numbers. Each number is a separate bridge login; replies always go out from the number that owns that chat.

Features (v1)

  • Inbound SMS via a shared HTTPS webhook (AAISP HTTP POST)
  • Outbound SMS via https://sms.aa.net.uk/sms.cgi
  • Multiple AA numbers for one Matrix/Beeper user
  • Bot login with number + outgoing SMS password
  • Start new chats with start-chat / Beeper provisioning (ResolveIdentifier)
  • SQLite persistence (bridgev2 default)
  • End-to-bridge encryption (Megolm) so a Beeper homeserver stores ciphertext

Not in v1: delivery reports, MMS/media, SMS groups, alpha sender IDs, history backfill.

Requirements

  • The published container image, or Go 1.27.3+ to build from source
  • A Beeper account (or any Matrix homeserver that can run an appservice)
  • One or more AAISP numbers that can send and receive SMS (07 recommended for inbound)
  • A public HTTPS URL reaching the bridge (reverse proxy, Cloudflare Tunnel, etc.)

Run

Published image (package), linux/amd64 and linux/arm64:

docker pull git.gac.sh/george/mautrix-aasms:latest

latest tracks the default branch. Each push is also tagged with its full commit SHA.

Generate a config, then run the bridge with that directory mounted at /data (config and the SQLite database live there; the bridge listens on port 29323):

mkdir -p data
docker run --rm -v "$PWD/data:/data" \
  git.gac.sh/george/mautrix-aasms:latest -e -c /data/config.yaml

docker run --rm -v "$PWD/data:/data" -p 29323:29323 \
  git.gac.sh/george/mautrix-aasms:latest

Build

From source (pure-Go olm via -tags goolm; SQLite still needs cgo):

./build.sh
# produces ./mautrix-aasms
./mautrix-aasms -e -c config.yaml

Or build the image locally:

docker build -t mautrix-aasms .

Beeper setup (bbctl)

  1. Install and log in with bbctl.

  2. Generate a bridgev2 config:

    bbctl config --type bridgev2 sh-aasms
    
  3. Merge / set at least:

    • appservice.public_address — public HTTPS base URL of this bridge
    • network.webhook_secret — random secret of at least 32 characters (openssl rand -hex 32)
    • homeserver / appservice credentials from bbctl
  4. Run the bridge with that config (binary or container). Keep it running (tmux/systemd/Docker).

  5. In Beeper, DM the bridge bot (localpart is typically aasmsbot / @sh-aasmsbot:beeper.local depending on your bbctl name) and run login.

  6. Enter the AA number and the outgoing SMS password from the AA control pages. Repeat login for each number you want to use.

Starting chats

  • Bot: start-chat <phone> (optionally prefix with a login ID if you have several)
  • Or use Beeper’s start-DM / phone lookup UI if it calls the provisioning API — this bridge implements ResolveIdentifier.

Chats are scoped to the current login. A DM started under +447…A always sends as A; the same person under number B is a separate room.

Contact names

Chats are titled with the phone number until Proton Contacts is connected. In the management room with the bridge bot:

  • contacts-login asks for the Proton username, then the password, then an authenticator code if the account has one. A separate mailbox password is asked for only when the account uses one. The bridge tries to remove password and code messages from the room. If Proton asks for human verification, the bot sends a verify.proton.me link. Complete that page in a browser, then send done.
  • contacts-sync downloads the address book again and renames chats.
  • contacts-status shows whether a Proton session is connected.
  • contacts-logout drops the session. Chats go back to phone numbers.

The bridge stores the Proton session in its database, restores it on startup, and refreshes contacts hourly. Each phone number on a contact is matched to that contact's name. Proton shows the session as an unknown application. A security key with no authenticator code cannot be completed in the bot.

The stored session can decrypt Proton mail as well as contacts. It is kept in the same database as the SMS passwords.

AAISP control pages

For each logged-in number:

  1. Open the number → Incoming → SMS inbound target.

  2. Set a single HTTPS URL (POST — do not end the URL with ? or &):

    https://bridge.example.com/_aasms/sms?token=YOUR_SECRET
    

    Use the same token as network.webhook_secret. All numbers can share this URL; the bridge routes by the da (destination) field.

  3. Ensure the outgoing SMS password matches what you entered at login.

API reference: SMS API.

End-to-bridge encryption

Portal rooms and the bridge management room use Matrix Megolm (m.megolm.v1.aes-sha2). The bridge encrypts and decrypts on the machine where it runs. Hungryserv stores ciphertext for those events.

bbctl config --type bridgev2 already sets encryption.allow, default, require, and appservice mode. A config generated with -e leaves encryption.allow false. On startup, if homeserver.software is hungry and encryption.allow is false, the bridge applies the same settings official self-hosted bridges use (appservice Megolm, cross-signed device trust, and key deletion after use). An explicit encryption.allow: true is kept as written.

Rooms created before encryption was on are switched to encrypted at the next start. Events already stored in plaintext on the homeserver stay plaintext.

SMS between this bridge and AAISP is still the carrier's plain text. The bridge reads the message in order to send it.

On a normal Synapse, set encryption.allow and encryption.default to true and leave encryption.appservice false. See the mautrix end-to-bridge encryption docs. The binary is built with pure-Go olm (-tags goolm).

Message model

Direction Behaviour
Inbound Webhook → normalize oa/da → find login for da → DM portal for oa
Outbound Matrix text in that DM → sms.cgi as the login’s number
Dedupe (oa, da, ud, scts) ignored for ~10 minutes on retries
Empty body Logged and dropped (HTTP 200 so AAISP does not retry forever)
Unknown da HTTP 200 + log (no Matrix event)
Send failure ERR:… from AAISP surfaced as a Matrix send error

Numbers are stored as E.164 (+447…). UK national (07…) and 447… are accepted on input.

Config notes

Network section (also embedded via -e):

network:
    webhook_secret: change-me

Inbound SMS is rejected while webhook_secret is shorter than 32 characters, which includes the change-me default. Generate one with openssl rand -hex 32.

appservice.public_address must be set or the bridge will refuse to start (webhook registration depends on it).

License

See repository metadata on git.sr.ht/~gc/mautrix-aasms.