+ You can run these commands on the phone via:
+
+ Directly on phone — open the Termux app and type commands
+ SSH — run sshd in Termux, then ssh -p 8022 phone-ip from your computer
+ scrcpy — mirror the phone screen to your computer via USB or WiFi
+
+
+ }
+ />
+
setCurrentStep(1)}>
Next: Connect to Phone
diff --git a/api/src/services/sms-device-monitor.service.ts b/api/src/services/sms-device-monitor.service.ts
index 2d678c5f..238414dd 100644
--- a/api/src/services/sms-device-monitor.service.ts
+++ b/api/src/services/sms-device-monitor.service.ts
@@ -9,10 +9,12 @@ class SmsDeviceMonitorService {
start() {
if (this.interval) return;
- // Initial check
- this.check().catch((err) => {
- logger.warn('Initial SMS device check failed:', err instanceof Error ? err.message : err);
- });
+ // Delay initial check by 15s to stagger with response sync (avoids concurrent Termux API calls)
+ setTimeout(() => {
+ this.check().catch((err) => {
+ logger.warn('Initial SMS device check failed:', err instanceof Error ? err.message : err);
+ });
+ }, 15_000);
this.interval = setInterval(() => {
this.check().catch((err) => {
@@ -20,7 +22,7 @@ class SmsDeviceMonitorService {
});
}, env.SMS_DEVICE_MONITOR_INTERVAL_MS);
- logger.info(`SMS device monitor started (interval: ${env.SMS_DEVICE_MONITOR_INTERVAL_MS}ms)`);
+ logger.info(`SMS device monitor started (interval: ${env.SMS_DEVICE_MONITOR_INTERVAL_MS}ms, initial delay: 15s)`);
}
stop() {
diff --git a/api/src/services/termux.client.ts b/api/src/services/termux.client.ts
index 8d9e2ec7..6fa50a54 100644
--- a/api/src/services/termux.client.ts
+++ b/api/src/services/termux.client.ts
@@ -222,7 +222,11 @@ class TermuxClient {
if (!this.enabled) return null;
try {
- return await this.request('GET', '/api/device/battery');
+ // Flask endpoint returns { battery: { percentage, status, ... }, success: true }
+ const data = await this.request<{ battery?: TermuxBatteryStatus } & Partial>(
+ 'GET', '/api/device/battery',
+ );
+ return data.battery || data as unknown as TermuxBatteryStatus;
} catch (err) {
logger.warn('Termux getBattery failed:', err instanceof Error ? err.message : err);
return null;
diff --git a/campaign_connector b/campaign_connector
new file mode 160000
index 00000000..30c2cfeb
--- /dev/null
+++ b/campaign_connector
@@ -0,0 +1 @@
+Subproject commit 30c2cfeba55a886b45878331c48346472d05e75a
diff --git a/mkdocs/.cache/plugin/social/assets/images/social/docs/admin/broadcast/sms.png b/mkdocs/.cache/plugin/social/assets/images/social/docs/admin/broadcast/sms.png
index eb66edc0..6c33b625 100644
Binary files a/mkdocs/.cache/plugin/social/assets/images/social/docs/admin/broadcast/sms.png and b/mkdocs/.cache/plugin/social/assets/images/social/docs/admin/broadcast/sms.png differ
diff --git a/mkdocs/.cache/plugin/social/manifest.json b/mkdocs/.cache/plugin/social/manifest.json
index b4f8db31..67292e61 100644
--- a/mkdocs/.cache/plugin/social/manifest.json
+++ b/mkdocs/.cache/plugin/social/manifest.json
@@ -28,7 +28,7 @@
"assets/images/social/docs/admin/broadcast/email-templates.png": "6b121b89ef5cea5b36f39032e3a50e9364e30bb3",
"assets/images/social/docs/admin/broadcast/index.png": "68d98e1866ae03ecde866e5f55aa6282cd44f941",
"assets/images/social/docs/admin/broadcast/newsletter.png": "31997f46a4f4a14ca1a688e9f54ff0c1f3552a78",
- "assets/images/social/docs/admin/broadcast/sms.png": "d331316eb6ebe63adf9c1f7ba61f73ecc51b6ced",
+ "assets/images/social/docs/admin/broadcast/sms.png": "82ab0ac704e93728f657d899c7373cd3078385d3",
"assets/images/social/docs/admin/dashboard.png": "8ea5f8248da65c60385ffcd06c32aebe2cf96717",
"assets/images/social/docs/admin/index.png": "5b118844de3687d26a701228b5e71762becf524c",
"assets/images/social/docs/admin/map/areas.png": "8bdf057cd91548b6bd037a7bcc6890ebe226f85a",
diff --git a/mkdocs/docs/docs/admin/broadcast/sms.md b/mkdocs/docs/docs/admin/broadcast/sms.md
index e488d2be..22ac00cf 100644
--- a/mkdocs/docs/docs/admin/broadcast/sms.md
+++ b/mkdocs/docs/docs/admin/broadcast/sms.md
@@ -1,64 +1,506 @@
---
title: SMS Campaigns
-description: Text message outreach via a Termux Android bridge with contact management and response tracking.
+description: Complete guide to setting up the Termux Android SMS bridge for text message outreach, contact management, and response tracking.
icon: material/message-text
---
# SMS Campaigns
-Text message outreach via a Termux Android bridge. Enable with `ENABLE_SMS=true`.
+Text message outreach via a Termux Android bridge. Uses a real Android phone to send and receive SMS — no third-party SMS gateway or Twilio account needed.
+
+Enable with `ENABLE_SMS=true` or via the setup wizard.
+
+---
+
+## Architecture Overview
+
+The SMS system uses a three-tier architecture where your server communicates with a lightweight Python Flask API running on an Android phone:
+
+```mermaid
+graph LR
+ A[Admin Dashboard Campaign UI] -->|API calls| B[Express API BullMQ Queue]
+ B -->|HTTP + API Key| C[Android Phone Flask on Termux]
+ C -->|termux-sms-send| D[Android SMS]
+ D -->|Carrier Network| E[Recipients]
+ E -->|Reply SMS| D
+ D -->|termux-sms-list| C
+ C -->|HTTP response| B
+```
+
+**Why this approach?**
+
+- **No SaaS dependency** — your phone is the SMS gateway, no Twilio/MessageBird/etc.
+- **Real phone number** — recipients see a real number, not a short code
+- **Two-way messaging** — incoming replies sync automatically
+- **Low cost** — just your phone plan's SMS allowance
+- **Full control** — FOSS stack end-to-end
+
+---
+
+## Prerequisites
+
+Before starting setup, you'll need:
+
+| Item | Details |
+|------|---------|
+| **Android phone** | Any Android 7+ device with an active SIM card and SMS plan |
+| **Termux** | Terminal emulator — install from **F-Droid** (not Play Store) |
+| **Termux:API** | Termux plugin for SMS/contacts/battery — install from **F-Droid** |
+| **Tailscale** (recommended) | VPN mesh for stable IP — install from Play Store |
+| **Network access** | Phone must be reachable from the server (Tailscale, LAN, or port forwarding) |
+
+!!! danger "Both Apps MUST Come from F-Droid"
+ The Play Store version of Termux is **abandoned and incompatible** with the API plugin. If you install Termux from the Play Store and Termux:API from F-Droid (or vice versa), SMS commands will fail with:
+
+ > Termux:API is not yet available on Google Play
+
+ **Fix:** Uninstall both apps, then reinstall **both** from [F-Droid](https://f-droid.org). They must come from the same source because Android verifies matching app signatures for inter-process communication.
+
+---
+
+## Phone Setup Guide
+
+### Step 1: Install Apps
+
+On your Android phone:
+
+1. **Install F-Droid** — download from [f-droid.org](https://f-droid.org) if you don't have it
+2. **Install Termux** — search in F-Droid and install
+3. **Install Termux:API** — search in F-Droid and install
+4. **Install Tailscale** (recommended) — from Play Store, create account, connect to your tailnet
+
+### Step 2: Set Up Termux
+
+Open the Termux app on your phone and run:
+
+```bash
+# Update package repository and install dependencies
+pkg update && pkg install python git termux-api openssh -y
+
+# Install the Flask web framework (used by the SMS server)
+pip install flask
+```
+
+!!! note "termux-api package"
+ The `termux-api` package provides the command-line tools (`termux-sms-send`, `termux-sms-list`, etc.) that bridge to the Termux:API Android app. You need **both** the F-Droid app and this Termux package installed.
+
+### Step 3: Clone the SMS Server
+
+The SMS server code is hosted on your Gitea instance. Clone it directly to the phone:
+
+```bash
+# Clone the campaign connector repository
+git clone https://gitea.bnkops.com/admin/campaign_connector.git ~/sms-server
+```
+
+!!! tip "Alternative: Copy just the server file"
+ If git isn't available or you prefer minimal setup, you only need one file:
+ ```bash
+ mkdir -p ~/sms-server/android
+ # Copy termux-sms-api-server.py from your server to the phone
+ # via SSH, USB transfer, or paste the contents directly
+ ```
+
+### Step 4: Grant Permissions
+
+Termux:API needs Android permissions to access SMS, contacts, and other features:
+
+```bash
+# These commands will trigger Android permission prompts
+# Tap "Allow" when prompted
+termux-sms-list # Triggers SMS permission
+termux-contact-list # Triggers Contacts permission
+termux-battery-status # Usually auto-granted
+```
+
+If you missed a prompt, go to **Android Settings → Apps → Termux:API → Permissions** and enable SMS and Contacts manually.
+
+### Step 5: Set the API Key
+
+Go to the admin dashboard SMS Setup page (`/app/sms/setup`) and click **Generate API Key**. Copy the generated key, then set it in Termux:
+
+```bash
+# Replace YOUR_KEY_HERE with the key from the setup wizard
+export SMS_API_SECRET='YOUR_KEY_HERE'
+
+# Save it permanently so it survives restarts
+echo 'export SMS_API_SECRET="YOUR_KEY_HERE"' >> ~/.bashrc
+```
+
+### Step 6: Start the Server
+
+```bash
+cd ~/sms-server/android
+python termux-sms-api-server.py
+```
+
+You should see output like:
+
+```
+🚀 Termux SMS API Server Starting
+📱 Device IP: 100.64.0.5
+🌐 API Base URL: http://100.64.0.5:5001
+🔗 Health Check: http://100.64.0.5:5001/health
+📞 SMS Endpoint: http://100.64.0.5:5001/api/sms/send
+```
+
+Note the **Device IP** — you'll need it for the connection wizard.
+
+### Step 7: Run Persistently
+
+By default, the server dies if you close Termux or restart the phone. Here's how to make it survive:
+
+#### A. Prevent Android from Killing Termux
+
+This is **required** regardless of which method you use below:
+
+1. Open **Android Settings → Apps → Termux → Battery** → set to **Unrestricted**
+2. Lock Termux in the recent apps view (long-press the app card → Lock/Pin)
+3. Samsung phones: also add Termux to **Settings → Device Care → Battery → Never Sleeping Apps**
+
+#### B. Install Termux:Boot (auto-start on reboot)
+
+Install [Termux:Boot](https://f-droid.org/packages/com.termux.boot/) from F-Droid (must be same source as Termux), then open it once to register with Android.
+
+```bash
+# Create the boot scripts directory
+mkdir -p ~/.termux/boot
+
+# Create the auto-start script
+cat > ~/.termux/boot/start-sms-server << 'EOF'
+#!/data/data/com.termux/files/usr/bin/sh
+termux-wake-lock
+export SMS_API_SECRET="YOUR_KEY_HERE"
+mkdir -p ~/logs
+nohup python ~/sms-server/android/termux-sms-api-server.py >> ~/logs/sms-api.log 2>&1 &
+EOF
+
+chmod +x ~/.termux/boot/start-sms-server
+```
+
+!!! warning "Replace YOUR_KEY_HERE"
+ Edit the script and replace `YOUR_KEY_HERE` with your actual API key. Or if you've already saved it in `~/.bashrc`, replace the export line with `source ~/.bashrc`.
+
+#### C. Watchdog (auto-restart on crash)
+
+The repo includes a watchdog script that monitors the server and restarts it if it crashes:
+
+```bash
+# Run the watchdog (it starts the server automatically)
+cd ~/sms-server/android
+bash sms-watchdog.sh
+```
+
+The watchdog:
+
+- Acquires a wake lock (`termux-wake-lock`) to prevent Android from sleeping
+- Checks the server health every 30 seconds
+- Automatically restarts the server if it's unresponsive
+- Logs everything to `~/logs/sms-api.log`
+
+For maximum reliability, use the watchdog as your boot script:
+
+```bash
+cat > ~/.termux/boot/start-sms-server << 'EOF'
+#!/data/data/com.termux/files/usr/bin/sh
+source ~/.bashrc
+nohup bash ~/sms-server/android/sms-watchdog.sh >> ~/logs/sms-watchdog.log 2>&1 &
+EOF
+
+chmod +x ~/.termux/boot/start-sms-server
+```
+
+#### D. Quick Background (simplest)
+
+If you just want to background the server without persistence:
+
+```bash
+# Start in background
+termux-wake-lock
+nohup python ~/sms-server/android/termux-sms-api-server.py >> ~/logs/sms-api.log 2>&1 &
+
+# Check if it's running
+curl http://127.0.0.1:5001/health
+
+# View logs
+tail -f ~/logs/sms-api.log
+
+# Stop the server
+pkill -f termux-sms-api-server.py
+```
+
+---
+
+## Accessing the Phone
+
+There are several ways to run commands on the phone:
+
+### Direct (on phone)
+Simply open the Termux app and type commands. Best for initial setup.
+
+### SSH (remote access)
+Start the SSH server in Termux, then connect from your computer:
+
+```bash
+# On the phone (first time only):
+pkg install openssh
+passwd # Set a password
+sshd # Start SSH server on port 8022
+
+# From your computer:
+ssh -p 8022 your-phone-ip
+# Or with Tailscale:
+ssh -p 8022 100.x.x.x
+```
+
+### scrcpy (screen mirror)
+Mirror the phone screen to your computer — great for setup:
+
+```bash
+# Install scrcpy on your computer (Ubuntu)
+sudo apt install scrcpy
+
+# Connect via USB
+scrcpy
+
+# Or wireless (phone must be on same network)
+scrcpy --tcpip=phone-ip:5555
+```
+
+---
+
+## Setup Wizard
+
+The admin panel provides a guided three-step wizard at `/app/sms/setup`:
+
+### Step 1: Prepare Phone
+
+Walks you through installing apps, cloning the server, setting the API key, and starting the Flask server. Generates a shared API key that both the server and phone use for authentication.
+
+### Step 2: Connect
+
+Choose how to find your phone's IP address:
+
+=== "Tailscale Auto-Discovery (Recommended)"
+
+ 1. Enter your Tailscale API key (`tskey-api-...`)
+ 2. Click **Discover Devices**
+ 3. The wizard queries the Tailscale API and lists all devices on your tailnet
+ 4. Select your Android phone — the URL auto-fills with its stable `100.x.x.x` IP
+
+ !!! info "Getting a Tailscale API Key"
+ Go to [Tailscale Admin Console](https://login.tailscale.com/admin/settings/keys) → Settings → Keys → Generate auth key or API access token.
+
+=== "Manual URL Entry"
+
+ Enter the phone's URL directly:
+
+ - **With Tailscale:** `http://100.x.x.x:5001` (stable IP, works across networks)
+ - **On same LAN:** `http://192.168.x.x:5001` (changes if phone reconnects)
+ - **Via port forward:** `http://your-public-ip:5001` (requires router config)
+
+### Step 3: Test & Save
+
+1. Click **Test Connection** — the wizard calls the phone's `/health` endpoint
+2. On success, you'll see device uptime and message count
+3. Click **Save Configuration** — stores the URL and key encrypted in the database
+4. The `enableSms` feature flag is automatically enabled
---
## How It Works
-1. **Configure the bridge** — set `TERMUX_API_URL` and `TERMUX_API_KEY` to connect to an Android device running Termux with the SMS plugin
-2. **Create contact lists** — import contacts or build lists from existing supporters
-3. **Write a campaign** — compose a message template with variable substitution (name, location, etc.)
-4. **Launch the campaign** — messages are queued via BullMQ and sent serially through the device
-5. **Monitor responses** — incoming replies are synced and classified by keyword
+### Sending Messages
+
+1. Admin creates an SMS campaign with a message template and contact list
+2. Campaign is started → messages are queued in **BullMQ** (one at a time, serial delivery)
+3. For each message, the Express API calls `POST /api/sms/send` on the phone
+4. The Flask server on the phone executes `termux-sms-send` to send via Android's native SMS
+5. A notification appears on the phone for each sent message
+6. Results are tracked in the database (success/failure per recipient)
+
+### Receiving Responses
+
+A background service (`sms-response-sync.service.ts`) polls the phone's inbox at a configurable interval:
+
+1. Calls `GET /api/sms/inbox?since=` on the phone
+2. The Flask server runs `termux-sms-list` to get new messages
+3. Incoming messages are matched to contacts and classified by keyword
+4. Threaded conversations are maintained per contact
+
+### Device Monitoring
+
+A background service (`sms-device-monitor.service.ts`) checks phone health periodically:
+
+- Battery level, charging status, temperature
+- Server uptime and total messages sent
+- Connection status (available/unreachable)
+- Results displayed on the SMS Dashboard
---
## Key Features
- **Contact lists** — import, tag, and segment contacts for targeted outreach
-- **Message templates** — reusable templates with variable placeholders
-- **BullMQ queue** — serial delivery with configurable delays between messages (`SMS_DELAY_BETWEEN_MS`)
-- **Response sync** — incoming SMS replies synced and classified automatically (`SMS_RESPONSE_SYNC_INTERVAL_MS`)
-- **Device monitoring** — health checks and status reporting for the connected Android device (`SMS_DEVICE_MONITOR_INTERVAL_MS`)
+- **Message templates** — reusable templates with `{name}` variable placeholders
+- **BullMQ queue** — serial delivery with configurable delays between messages
+- **Response sync** — incoming SMS replies synced and classified automatically
+- **Device monitoring** — battery, uptime, and connectivity reported in real-time
- **Conversation view** — threaded message history per contact
-- **Retry logic** — configurable retry attempts for failed deliveries (`SMS_MAX_RETRIES`)
-
----
-
-## Setup Wizard
-
-The SMS Setup page (`/app/sms/setup`) provides a guided three-step wizard for connecting your Android phone:
-
-### Step 1: Prepare Phone
-
-Install Termux and Termux:API from F-Droid on the Android device, then generate a shared API key from the admin panel. The key is used for mutual authentication between the server and the phone.
-
-### Step 2: Connect
-
-Choose one of two connection methods:
-
-- **Tailscale Auto-Discovery (recommended)** -- enter your Tailscale API key and the wizard automatically discovers devices on your tailnet, highlights Android devices, and pre-fills the stable IP address
-- **Manual URL Entry** -- enter the Termux API server URL directly (typically `http://100.x.x.x:5001` when using Tailscale)
-
-### Step 3: Test and Save
-
-Run a live connection test against the phone to verify the URL and API key are correct. The test displays device health info (uptime, messages sent). Once the test passes, save the configuration to enable SMS features platform-wide.
-
-The wizard stores credentials encrypted in the database and updates the `enableSms` feature flag automatically.
+- **Retry logic** — configurable retry attempts for failed deliveries
---
## Admin Routes
-- `/app/sms/setup` -- guided setup wizard with Tailscale auto-discovery
-- `/app/sms` -- SMS dashboard with campaign overview and device status
-- `/app/sms/contacts` -- manage contact lists and entries
-- `/app/sms/campaigns` -- create and monitor SMS campaigns
-- `/app/sms/conversations` -- view threaded conversations with contacts
+| Route | Description |
+|-------|-------------|
+| `/app/sms/setup` | Guided setup wizard with Tailscale auto-discovery |
+| `/app/sms` | SMS dashboard — campaign overview and device status |
+| `/app/sms/contacts` | Manage contact lists and entries |
+| `/app/sms/campaigns` | Create and monitor SMS campaigns |
+| `/app/sms/conversations` | View threaded conversations with contacts |
+
+---
+
+## Configuration
+
+### Environment Variables
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `ENABLE_SMS` | `false` | Feature flag (also set via setup wizard) |
+| `TERMUX_API_URL` | — | Phone URL, e.g. `http://100.x.x.x:5001` |
+| `TERMUX_API_KEY` | — | Shared API key for authentication |
+| `SMS_DELAY_BETWEEN_MS` | `1000` | Delay between messages in a campaign (ms) |
+| `SMS_MAX_RETRIES` | `3` | Retry attempts for failed sends |
+| `SMS_RESPONSE_SYNC_INTERVAL_MS` | `10000` | How often to check for incoming replies (ms) |
+| `SMS_DEVICE_MONITOR_INTERVAL_MS` | `30000` | How often to check device health (ms) |
+| `TAILSCALE_API_KEY` | — | Tailscale API key for auto-discovery |
+| `TAILSCALE_TAILNET` | — | Tailscale tailnet name (optional) |
+
+!!! note
+ When you use the setup wizard, configuration is stored in the database and takes priority over environment variables. You don't need to set env vars if you use the wizard.
+
+### Phone-Side Configuration
+
+On the phone, only one environment variable is needed:
+
+```bash
+export SMS_API_SECRET='your-64-char-hex-key'
+```
+
+The Flask server also accepts `TERMUX_API_KEY` as an alias for backwards compatibility.
+
+---
+
+## Phone API Endpoints
+
+The Flask server running on the phone exposes these endpoints on port 5001:
+
+| Method | Endpoint | Auth | Description |
+|--------|----------|------|-------------|
+| `GET` | `/health` | No | Server status, uptime, messages sent |
+| `GET` | `/` | No | Web dashboard with endpoint documentation |
+| `POST` | `/api/sms/send` | Yes | Send an SMS message |
+| `POST` | `/api/sms/send-reply` | Yes | Send a reply with conversation tracking |
+| `GET` | `/api/sms/inbox` | No | Get incoming messages (with `since` filter) |
+| `GET` | `/api/sms/list` | No | List messages with pagination |
+| `GET` | `/api/sms/history` | No | Get SMS history for a phone number |
+| `GET` | `/api/device/battery` | No | Battery level, health, temperature |
+| `GET` | `/api/device/location` | No | GPS coordinates (requires permission) |
+| `GET` | `/api/device/info` | No | Device info + battery + uptime |
+| `GET` | `/api/contacts/list` | No | Phone address book (with search) |
+| `POST` | `/api/campaign/notify` | No | Push notification to device |
+
+Authentication uses the `X-API-Key` header with the shared secret.
+
+---
+
+## Troubleshooting
+
+### Phone can't be reached
+
+**Symptoms:** Test connection fails, "Connection refused" or timeout.
+
+**Checks:**
+
+1. **Is the Flask server running?** Check Termux — you should see the startup banner
+2. **Is the IP correct?** Run `ifconfig` in Termux to find the current IP
+3. **Are they on the same network?** If not using Tailscale, both must be on the same LAN
+4. **Is Tailscale connected?** Check the Tailscale app on the phone — it should show "Connected"
+5. **Firewall?** Android rarely blocks incoming connections on Termux, but check if any firewall app is installed
+
+```bash
+# Quick test from your server
+curl http://PHONE_IP:5001/health
+```
+
+### "Authentication required" errors
+
+**Symptoms:** API calls return 401 with "Authentication required".
+
+**Fix:** The API key on the phone doesn't match the one in the admin panel.
+
+```bash
+# On the phone, check the current key
+echo $SMS_API_SECRET
+
+# If it doesn't match, update it
+export SMS_API_SECRET='correct-key-from-admin-panel'
+echo 'export SMS_API_SECRET="correct-key-from-admin-panel"' >> ~/.bashrc
+
+# Restart the server
+pkill -f termux-sms-api-server.py
+cd ~/sms-server/android && python termux-sms-api-server.py
+```
+
+### SMS not sending
+
+**Symptoms:** Server responds successfully but messages don't arrive.
+
+**Checks:**
+
+1. **SMS permissions granted?** Go to Android Settings → Apps → Termux:API → Permissions → SMS
+2. **Active SIM card?** The phone needs a working SIM with SMS capability
+3. **Message too long?** Maximum 1600 characters per message
+4. **Rate limited?** Minimum 1 second between messages (carrier may enforce longer delays)
+
+### Termux keeps getting killed
+
+**Symptoms:** Server stops after some time, especially when phone screen is off.
+
+**Fix:** Disable battery optimization for Termux:
+
+1. Android Settings → Apps → Termux → Battery → **Unrestricted**
+2. Lock Termux in recent apps (long-press app card → Lock)
+3. Some phones: Settings → Battery → Battery Optimization → find Termux → Don't Optimize
+4. Samsung: Settings → Device Care → Battery → App Power Management → add Termux to "Never sleeping apps"
+
+### Server won't start — "Missing SMS_API_SECRET"
+
+**Symptoms:** Server exits immediately with a security error.
+
+**Fix:** Set the API key environment variable:
+
+```bash
+# Generate a new key if you don't have one
+python -c "import secrets; print(secrets.token_hex(32))"
+
+# Set it
+export SMS_API_SECRET='your-generated-key'
+echo 'export SMS_API_SECRET="your-key"' >> ~/.bashrc
+```
+
+### Updating the SMS server
+
+To pull the latest version of the server code:
+
+```bash
+cd ~/sms-server
+git pull
+
+# Restart the server
+pkill -f termux-sms-api-server.py
+cd android && python termux-sms-api-server.py
+```