# Sevenko SMS Bridge

Turn an Android phone with a SIM into an SMS gateway: send SMS via a REST API, receive SMS via webhook, run keyword-triggered automations, all managed from a web dashboard.

**Live:** https://sms.sevenkoapps.store

**Docs:** [`API.md`](API.md) — public REST API reference · [`NOTES.md`](NOTES.md) — detailed build/ops notes · this file — quickstart

| Piece | Where |
|---|---|
| Backend API + dashboard | host systemd service `sms-bridge.service` → `server/index.js` (port 4090) |
| Database | `data/smsbridge.db` (node:sqlite, WAL) |
| Android bridge app | `android/` (pure framework, zero deps) |
| APK download | https://sms.sevenkoapps.store/apk/latest.apk |
| Release keystore | `keystore/smsbridge-release.keystore` (pass in `~/.secrets/smsbridge-keystore.env`) |
| Off-box keystore backup | `/home/administrator/cs001-backup-aug9/smsbridge-release.keystore` |

---

## Architecture

```
   ┌────────────┐   API key (x-api-key)        ┌─────────────────┐
   │ any client │ ─── POST /api/send ────────▶ │  sms-bridge     │
   └────────────┘                              │  server (4090)  │
                                               │   . msg queue   │
   ┌────────────┐   x-device-key             │   . api keys    │
   │ Android    │ ◀── GET /api/device/pending ─│   . keywords    │
   │ bridge app │ ─── report / incoming ──────▶ │   . webhooks    │
   └────────────┘   (self-registers)              └────────┬────────┘
         │  SmsManager (SEND_SMS)                          │ inbound via webhooks
         │  SmsReceiver (RECEIVE_SMS)                       ▼
                                        your webhook/scripts/autorun
```

- **Send:** client POSTs `/api/send` → row queued in DB → Android polls `/api/device/pending` → sends via `SmsManager` → reports `sent|delivered|failed`.
- **Receive:** Android `SmsReceiver` catches inbound SMS → POSTs `/api/device/incoming` → server dedupes, stores, fires global webhook + keyword rules, queues auto-replies.
- **Zero-config device auth:** the app generates a random persistent `device_key` (stored in SharedPreferences) and sends it as `x-device-key`. On first contact the server auto-registers the phone (name `phone-<key8>`). No token setup — the app works out of the box once you enter the server URL.
- **Keywords:** exact / starts / contains. Each rule can fire a webhook, a server-side script, and/or an SMS auto-reply.
- **Webhooks:** signed `X-SMSBridge-Signature: sha256=HMAC(secret,body)`, retried ×2 (10s, 60s), all attempts logged.

## Public API (API-key auth)

Header: `x-api-key: <key>`. Scopes: `send`, `read`.

```bash
# Send an SMS (queues; device delivers it)
curl -X POST https://sms.sevenkoapps.store/api/send \
  -H "x-api-key: <KEY>" -H "Content-Type: application/json" \
  -d '{"to":"+6421XXXXXXX","body":"hello"}'
# → {"ok":true,"id":7,"status":"queued"}

# List messages
curl -H "x-api-key: <KEY>" \
  "https://sms.sevenkoapps.store/api/messages?direction=in&limit=50"
```

## Dashboard

https://sms.sevenkoapps.store — admin password: `~/.secrets/sms-bridge.env` (`SMSBRIDGE_ADMIN_PASSWORD`).

Tabs: **Inbox & Send** · **API Keys** · **Devices** · **Keywords** · **Webhooks & Scripts** · **Event Log**.

- **Devices tab** → install the APK on the phone and press Start; it auto-registers here (no token). Download via the button in that tab.
- **API Keys tab** → create send/read keys (shown once).
- **Keywords tab** → keyword + optional webhook URL + optional script filename + optional SMS reply.
- **Webhooks tab** → global inbound webhook URL + HMAC secret + script editor.

## Android setup

1. Download `https://sms.sevenkoapps.store/apk/latest.apk` on the phone; install (allow unknown sources).
2. Open **SMS Bridge** app; enter only the server `https://sms.sevenkoapps.store`.
3. Grant **SMS** permission (Send + Receive), tap **Start bridge**.
4. Done — the phone generates its own device ID and auto-registers on the server (no token needed).
5. Disable battery optimisation for the app so it keeps polling in the background.

## Keyword script autorun

Scripts live in `data/scripts/*.sh` (editable via dashboard Webhooks tab). Run as user `administrator`, env:

```
SMS_FROM, SMS_BODY, SMS_KEYWORD, SMS_ID, SMS_TIMESTAMP
```

Example `status.sh`:

```bash
#!/usr/bin/env bash
echo "SMS from $SMS_FROM: $SMS_BODY at $SMS_TIMESTAMP" >> /tmp/sms.log
```

## Operations

```bash
systemctl status sms-bridge          # backend
sudo journalctl -u sms-bridge -f     # logs
sudo systemctl restart sms-bridge
```

Rebuild Android APK:

```bash
cd /home/administrator/sms-bridge/android
# Signing creds live in android/gradle.properties (gitignored). Template:
#   cp gradle.properties.example gradle.properties  # then fill in real paths/passwords
export ANDROID_HOME=/opt/android-sdk
./gradlew assembleRelease --no-daemon
/home/administrator/sms-bridge/scripts/publish_apk.sh "notes"
```

## Security notes

- Dashboard is password + HttpOnly session cookie; login throttled (5 fails → 15 min lockout).
- API keys stored as SHA-256 hashes; shown once on creation.
- **Device auth is zero-config**: the app sends a random persistent `x-device-key`; server auto-registers it. Anyone with network access to the API could register a device, so the API is exposed only via Caddy HTTPS (not directly on the internet) and the service binds to docker bridges in ufw.
- Service binds 0.0.0.0:4090 but ufw only allows 172.19/172.18 docker bridges; public access solely via Caddy HTTPS at `sms.sevenkoapps.store`.
- Keystore + admin password in `~/.secrets/` (no plaintext in repo/markdown).
- Zero runtime npm/external deps (Node stdlib `node:sqlite` + platform Android APIs).