← SMS Bridge home · Download Markdown · Download kit ZIP

1.1.0-beta-snapshot

SMS Bridge — self-hosted BETA how-to

Snapshot: 1.1.0-beta (see VERSION). Not 1.2.0.

Status: experimental personal/self-host beta packaging.

Not a public product launch. Not security-reviewed. Not available on Google Play / app stores.

Licensing: decision pending — this tree does not grant open-source, commercial, or redistribution rights; third-party Android/Python/OS components keep their own licenses. See LICENSE-DECISION-PENDING.txt.

This guide packages what was recoverable without touching a live phone bridge, live reverse proxy, or personal message data.

What this is

A single-owner, self-hosted relay:

1. Android companion app (sideloaded) on one phone you control

2. Your Linux Python bridge + SQLite on a host you control

3. HTTPS front door (e.g. Caddy) on a dedicated hostname you control

4. Local stdio MCP adapter on the assistant box, holding an access key in a protected file

5. Assistant connector registration (account-local; may apply to multiple bots under the same owner — confirm in UI)

There is no multi-tenant hosted service in this design. Do not share one MCP access key or one device pairing across unrelated people.

Verified vs not (honest)

CapabilityEvidence in this **1.1.0** snapshot
Individual SMS send through phoneVerified once on operator's Samsung (self-send test)
SMS + MMS **text** read, including **group MMS** participants / `is_group` / protocolsVerified on **one** Samsung after 1.1.0 (not claimed universal)
Feature scopeSMS + MMS text only; single-recipient SMS send; attachment **types** may list, bytes not retrieved; other chat transports out of scope
MMS image/attachment retrieval**Not in 1.1.0** (tracked separately as 1.2.0 work)
Auto-start after boot / update / app-open**Best-effort only** in 1.1.0 (`BootReceiver`); after APK updates reopen + **Start bridge**. Improved auto-start is 1.2.0 work — not claimed here
HTTP→HTTPS redirect quirksSeen on one personal deployment; **not** a generic claim
Security reviewInformal threat model only — **not** a formal audit

Unit tests in android-companion/ cover pure MsgLogic merge/address helpers only.

Architecture

[Assistant / Grok Bot]
    |  stdio MCP (local adapter + token file)
    v
[Your HTTPS hostname]  --Caddy/TLS-->  [127.0.0.1 Python bridge + SQLite]
    ^                                         |
    |                         long-poll /device/poll + /device/result
    +---------------- [Android companion app on phone] ------------------+

Prerequisites

Bundle layout

HOW_TO_SELF_HOST_BETA.md          ← this file
LICENSE-DECISION-PENDING.txt
THIRD_PARTY_NOTICES.md
MANIFEST.sha256
android-companion/                ← sanitized source (package org.smsbridge.companion)
binaries/SMSBridge-Companion-1.1.0-beta.apk  ← optional prebuilt companion
server/                           ← sanitized Python bridge, pair/revoke, tests, unit + Caddy templates
mcp-adapter/sms-bridge-mcp-stdio.py
docs/PHONE_API_CONTRACT.md
docs/THREAT_MODEL.md
docs/ROLLBACK.md
scripts/bootstrap-mcp-token.sh
scripts/run-isolated-server-tests.sh
scripts/security-scan.sh
EXCLUSIONS.md

Excluded on purpose: personal APK signed for a private operator package id, keystores, token files, SQLite DBs, message logs, contacts, live Caddy with real host/IP, build caches.

Personal prebuilt APKs defaulted to a real hostname and a personal package/signature — do not redistribute those.

From android-companion/:

./run-unit-tests.sh
./build.sh
# → out/SMSBridge-Companion-1.1.0-beta.apk

build.sh creates a new keystore under ~/.secrets/smsbridge-beta-companion/ by default (outside the ZIP). Keep the .jks / pass private.

Default server URL in the companion is empty — the operator types https://their-host before pairing.

Alternative: source-only

If you cannot build here, ship source + these instructions; recipients build with Android build-tools 35 + JDK 17.

2) Server + HTTPS

Sources are in server/ (Python 3 stdlib only).

mkdir -p ~/sms-bridge && cp server/server.py server/pair.sh server/revoke.sh ~/sms-bridge/
chmod 755 ~/sms-bridge/*.sh
export SMSB_HOME=~/sms-bridge
# Generate MCP token + hash (writes mode-0600 files; do not paste token into chat)
./scripts/bootstrap-mcp-token.sh
# Optional: copy hash only to the bridge host if token file lives elsewhere
mkdir -p ~/.config/systemd/user
cp server/systemd/sms-bridge.service ~/.config/systemd/user/
# Edit the unit: set SMSB_PROXY to your reverse-proxy peer IP when proxied (often 127.0.0.1)
systemctl --user daemon-reload
systemctl --user enable --now sms-bridge.service

Reverse proxy: start from server/caddy/Caddyfile.example with a dedicated hostname.

SMSB_PROXY defaults to empty (fail-closed) — X-Forwarded-For is ignored until you set it.

If you omit SMSB_PROXY behind a proxy on a private IP, LAN test routes (/api/health, /) can leak on the public hostname — set it.

Public paths only: /status, /device/* (device bearer), /mcp (MCP bearer).

Localhost admin: pair.sh / revoke.sh → /admin/*.

Phone API: docs/PHONE_API_CONTRACT.md.

Isolated synthetic tests (no phone): ./scripts/run-isolated-server-tests.sh.

3) Install on Android (sideload)

1. Transfer the companion APK to the phone; allow install from that source.

2. Open App info for SMS Bridge.

3. Grant SMS / Phone / Notifications as prompted.

4. If allowing SMS says the app was denied and suggests restricted access:

- Leave Permissions, return to the main App info screen

- Use the ⋮ / three-dot menu → Allow restricted settings

- Authenticate, then grant SMS again

5. Battery → Unrestricted (OEM names vary).

6. Enter your HTTPS base URL (no path) and the one-time pairing code from the server.

7. Tap Pair this phone, then Start bridge.

8. Confirm the persistent foreground notification ("SMS Bridge running").

After every APK update: reopen the app, confirm version, tap Start bridge again (service may not auto-resume).

4) Register the MCP connector

1. Place mcp-adapter/sms-bridge-mcp-stdio.py on the assistant machine.

2. Write the MCP token to a protected file (e.g. chmod 600).

3. Register a custom MCP command approximately like:

command: python3
args:    ["/absolute/path/sms-bridge-mcp-stdio.py"]
env:
  SMSB_MCP_URL=https://smsbridge.example/mcp
  SMSB_MCP_TOKEN_FILE=/absolute/path/mcp-access.token

4. Confirm registration in the product UI. Connector config may be account-local (shared across that owner's bots) — verify before enabling broadly.

5. Wait until tools finish loading; then call health/status first.

Never paste device tokens, pairing codes, or MCP keys into chat.

1. Health / status — phone online, no send.

2. List threads — expect SMS and (when present) MMS; group threads show is_group / multiple participants.

3. Read one thread the owner named — use completion/status polling if a command was queued; do not blindly re-issue duplicates.

4. Send only after explicit owner approval of recipient + exact body.

Verification codes and OTPs: keep out of chat and durable logs whenever possible.

6) Operations notes

7) Rollback / uninstall

See docs/ROLLBACK.md.

Threat model (summary)

See docs/THREAT_MODEL.md. Short version: anyone who can call /mcp with your key, or control the phone app / server disk, can read SMS/MMS text the phone can see and can queue sends the product policy allows. TLS protects the path; it is not E2EE. Sideloaded SMS apps are sensitive — treat the APK signing key as critical.

Remaining gaps (honest)

1. Operator license choice still pending (LICENSE-DECISION-PENDING.txt).

2. Broader device testing beyond one Samsung.

3. Formal security review — not done.

4. Distribution decision (direct APK only vs any store attempt — Play SMS policy is hostile to this use case).

5. Not a multi-tenant hosted product; each owner self-hosts one phone account.