gsmnode

gsmnode

Docker app from Tajniak81's Repository

Overview

gsmnode all-in-one: PocketBase, the API Server and the Web App in one container. Turn a spare Android phone into an SMS/call gateway and drive it from a self-hosted web app or the REST API. On first boot PocketBase upserts its superuser from PB_ADMIN_*, the API Server reconciles the database schema, and — if you fill them in — creates the first app login from GSMNODE_SUPERADMIN_*. Every step is idempotent, so restarts are safe. Only GSMNODE_SUPERADMIN has access to API Server panel. PocketBase admin: http://IP:8070]/_/ API Server + panel: http://IP:8080/ Web App: http://IP:8090/ https://github.com/gsmnode/gsmnode Android Phone Agent: https://github.com/gsmnode/phone-agent/releases Android Phone App: https://github.com/gsmnode/phone-app/releases Unraid Plugin: https://github.com/gsmnode/gsmnode-Unraid Home Assistant Integration: https://github.com/gsmnode/gsmnode-ha Keep both appdata paths on persistent storage — the /data volume holds plugin config with credentials such as IMAP/SMTP logins, so treat it like the database and back it up.
gsmnode

License API Server Web App Phone Apps Data Deploy

Surfaces  ·  Screenshots  ·  Run order  ·  Docker  ·  Webhooks  ·  Docs

Download the Phone App    Download the Phone Agent

Turn Android phones into a programmable SMS gateway, controlled through a web UI and a REST API.

The shared design system is signal-green #2E9E6B on ink, Space Grotesk (display) · IBM Plex Sans (body) · JetBrains Mono (code/labels), the lowercase gsm+node wordmark, and the two-arrow routing mark. The source design kit is not part of this repository; the tokens are implemented per surface (theme.js, style.css, theme.dart) and the exported brand images ship where a surface needs to render them. Every UI surface implements it (the Web App, API panel and Phone App share a persisted light/dark/system preference under the key gsmnode-theme; the Phone Agent follows the system theme; the Home Assistant Plugin ships the mark and both wordmark variants in its brand/ folder).

The application surfaces sit in front of a shared PocketBase. The API Server is the only component that talks to PocketBase; every other surface talks only to the API Server.

┌────────────┐        ┌──────────────────┐        ┌──────────────┐
│  Web App   │───────►│                  │        │              │
│ (Vue/Go)   │        │                  │        │              │
├────────────┤        │                  │        │              │
│ Phone App  │───────►│                  │        │              │
│ (Flutter)  │        │   API Server     │        │  PocketBase  │
├────────────┤        │      (Go)        │        │    (:8028)   │
│Phone Agent │───────►│                  │───────►│              │
│ (Flutter)  │        │                  │        │              │
├────────────┤        │                  │        │              │
│ HA plugin  │◄──────►│                  │        │              │
│  (Python)  │        │                  │        │              │
├────────────┤        │                  │        │              │
│Unraid plug.│───────►│                  │        │              │
│   (bash)   │        │                  │        │              │
└────────────┘        └──────────────────┘        └──────────────┘

Two distinct phone-side surfaces, deliberately kept separate:

  • Phone Agentcontrols the phone: sends/receives SMS & MMS and makes/receives calls on behalf of the gateway.
  • Phone Appcontrols the gateway: a mobile mirror of the Web App, with the same screens and the same functionality. It can optionally sit behind the phone's own face/fingerprint lock (Settings → App lock).

They install separately (app.gsmnode.phoneagent and app.gsmnode.phoneapp) and can sit side by side on one device.

The Home Assistant Plugin is the fourth client, and the only one that is both: it sends SMS and places calls from automations, watches the gateway and each phone as connectivity sensors, subscribes itself to the gateway's webhooks so incoming messages and calls become Home Assistant events, and can put either of the existing overviews in Home Assistant's sidebar. It is configured entirely from the Home Assistant UI — nothing goes in configuration.yaml.

The Unraid Plugin is the fifth, and the smallest: it adds a gsmnode notification agent to Unraid, so the server's own notifications go out as SMS. It only sends. Unraid hosts it the way it hosts its Telegram and Discord agents, so it is configured under Settings → Notifications like those are.

Surfaces

Folder Stack Port Status
API Server/ Go :8080 ✅ Built & verified (live E2E)
Web App/ Go BFF + Vue 3 + Tailwind :8090 ✅ Built & verified
Phone Agent/ Flutter (Android) ✅ Built & run on a real device; foreground service + delivery reports
Phone App/ Flutter (Android) ✅ Built — mobile mirror of the Web App; not yet run against a live server
Home Assistant Plugin/ HA custom component (Python) ✅ UI-only (no YAML): config flow, send_sms/call services, gateway + per-phone sensors, incoming events, notify entity, optional sidebar panel
Unraid Plugin/ Unraid plugin (.plg + bash) ✅ Built & unit-tested; not yet installed on a live server

Screenshots

Web App — dashboard, messages, inbox, devices, plugins Phone App — gateway mirror
API panel — endpoint reference, plugins, admin Phone Agent — sends/receives SMS and calls

Home Assistant — services, sensors, events, sidebar

Placeholders — drop real captures into assets/screenshots/ and they render here.

PocketBase collections

Managed by API Server/scripts/setup-pocketbase.mjs (idempotent):

  • users — auth (existing default collection) + role, organization, pluginSettings
  • organizationsname, pluginSettings (tenants; pluginSettings holds the org layer of the plugin cascade)
  • devicesdevice_id, name, platform, app_version, push_token, auth_token, status, last_seen_at, owner
  • messagesphone_numbers, text_message, type (sms/call/data/mms), sim_number, status, error, data_payload, data_port, subject, attachments, encrypted, schedule_at, sent_at, delivered_at, device, owner
  • inboxphone_number, message, type (sms/data/mms), received_at, sim_slot, data_payload, data_port, subject, attachments, encrypted, device, owner
  • callsphone_number, direction, status, sim_slot, duration, started_at, device, owner
  • webhooksevent, url, device, owner

Collections are locked to superuser access; the API Server enforces per-user ownership in application logic.

Run order

Building from source, against a PocketBase you run yourself. To skip all of this and bring the server side up in containers instead, jump to Deploy with Docker.

git clone https://github.com/gsmnode/gsmnode.git
cd gsmnode
  1. PocketBase v0.23+ — run it however you like (the official binary, or the image built by Docker/pocketbase/) and note its URL; the rest of this assumes http://localhost:8028. Create its superuser on first launch — that is the PB_ADMIN_* login below.

  2. API Server (:8080):

    cd "API Server"
    Copy-Item .env.example .env   # set POCKETBASE_URL and PB_ADMIN_*
    node scripts/setup-pocketbase.mjs           # one-time schema setup
    node scripts/create-user.mjs you@example.com "password" "Your Name"
    ./scripts/Run-ApiServer.ps1
    
  3. Web App (:8090):

    cd "Web App"; ./server/Run-WebApp.ps1
    

    Open http://localhost:8090 and sign in.

  4. Phone Agent — see Phone Agent/README.md (install Flutter + JDK 17, flutter create, copy android_overlay/, flutter run).

    Mirrored as its own repo at gsmnode/phone-agent, regenerated by scripts/publish-phone-agent.sh; the process is written down in Phone Agent/PUBLISHING.md.

  5. Phone App (optional) — see Phone App/README.md (flutter pub get, flutter run, then point it at the API Server on the sign-in screen).

    Mirrored as its own repo at gsmnode/phone-app, regenerated by scripts/publish-phone-app.sh; the process is written down in Phone App/PUBLISHING.md.

  6. Home Assistant Plugin (optional) — install it through HACS from gsmnode/gsmnode-ha, or copy Home Assistant Plugin/custom_components/gsmnode/ into <config>/custom_components/ by hand. Restart Home Assistant, then add gsmnode under Settings → Devices & Services. See Home Assistant Plugin/README.md.

    That GitHub repository is this folder, published on its own — HACS installs only from a repository whose root is the integration. It is regenerated by scripts/publish-ha-plugin.sh; the process is written down in Home Assistant Plugin/PUBLISHING.md.

  7. Unraid Plugin (optional) — on the Unraid server, Plugins → Install Plugin, and paste https://raw.githubusercontent.com/gsmnode/gsmnode-unraid/main/gsmnode.plg. Then fill the agent in under Settings → Notifications → Notification Agents, and tick Agents on the severities you want texted. See Unraid Plugin/README.md.

    Published the same way, from scripts/publish-unraid-plugin.sh to gsmnode/gsmnode-unraid — Unraid installs a plugin from a URL that serves the .plg and nothing else.

Deploy with Docker

The run order above builds from source against a PocketBase you run yourself. To bring the whole server side up in containers instead, pick one of two shapes — both carry a docker-compose.yml that builds from this working tree, and a docker-compose.prod.yml that pulls prebuilt images instead of building:

Layout Use when
Docker/ Three containers — PocketBase, API Server, Web App You want to scale, replace or upgrade the pieces independently. The only one with published images, so the only one you can run without building
Docker-AIO/ One container, all three under supervisord (nginx serves the SPA) One host, one thing to run

To build from this tree:

cd Docker                      # or Docker-AIO
Copy-Item .env.example .env
# edit .env: PB_ADMIN_* and GSMNODE_SUPERADMIN_* at minimum
docker compose up -d --build

Or, for Docker/ only, to pull prebuilt images instead of building — docker-compose.prod.yml and .env.prod.example are the only two files you need, so this works without cloning the repository:

cd Docker
Copy-Item .env.prod.example .env
# edit .env: PB_ADMIN_* and GSMNODE_SUPERADMIN_* at minimum
docker compose -f docker-compose.prod.yml up -d

Either way the Web App ends up on :8090, the API Server and its panel on :8080, and PocketBase's admin UI on :8070/_/. Nothing needs seeding by hand: PocketBase upserts its superuser from PB_ADMIN_*, and the API Server then reconciles the schema and creates the app super-admin from GSMNODE_SUPERADMIN_*. Every step is idempotent, so restarts and upgrades are safe.

Published images

Three images, on both registries, linux/amd64 only — on arm64 (Raspberry Pi, Apple Silicon) build from source with the first recipe above. Docker-AIO/ has no published image; it always builds.

GHCR (the default) Docker Hub
API Server ghcr.io/tajniak81/gsmnode-api-server tajniak81/gsmnode-api-server
Web App ghcr.io/tajniak81/gsmnode-web-app tajniak81/gsmnode-web-app
PocketBase ghcr.io/tajniak81/pocketbase-docker-root tajniak81/pocketbase-docker-root

docker-compose.prod.yml defaults to the GHCR ones at :latest. Set PB_IMAGE / API_IMAGE / WEB_IMAGE in .env to pin a version, switch to Docker Hub, or point at your own build — the gsmnode images also carry 0, 0.0, 0.0.4 and the short commit sha, and the PocketBase one tracks PocketBase's own version.

Docker/docker-compose.prod.yml binds the API panel and the PocketBase admin UI to localhost, leaving only the Web App on the network (PB_BIND / API_BIND open them up). The all-in-one publishes all three on every interface — put it behind a reverse proxy, or don't expose :8070 and :8080 on an untrusted one.

Two volumes hold everything worth keeping, and both want backing up: pb_data is the database, and api_data is the API Server's plugin state — plugins.json plus the settings the panel persists. Plugin config holds credentials such as IMAP/SMTP logins, so treat it as sensitively as the database.

The phone surfaces are not containerized — they are Android apps, installed on the phone (see their READMEs).

Message lifecycle

Pending → (device pulls) ProcessedSentDelivered · or Failed.

Webhooks

Events sms:received, sms:sent, sms:delivered, sms:failed, sms:data-received, mms:received, mms:downloaded, call:received, call:sent, call:failed are POSTed to registered URLs as {event, device_id, payload, created_at}. The Home Assistant Plugin subscribes itself to whichever of these you tick and re-fires them on Home Assistant's event bus.

Plugins & Email-to-SMS

The API Server has a plugin system (built-in Go connectors + external HTTP plugins), managed by a superadmin in the panel's Plugins section or under /api/admin/plugins*. Built-in config persists to plugins.json. See API Server/internal/plugins/README.md.

The first built-in is Email-to-SMS (modelled on sms-gate.app): an email to <phone>@<domain> becomes an outbound SMS. Two intake modes:

  • SMTP — the plugin runs an SMTP server; the sender authenticates (AUTH PLAIN) with their gsmnode login and the SMS is owned by that user.
  • IMAP — the plugin polls each user's own mailbox. Users connect their mailbox in the Web App (Settings → Integrations), resolved through a global → org → user cascade (/api/integrations/email-to-sms).

Per-user settings are generic: a plugin declares the fields it accepts (UserConfigurable, or a userConfig block in an external plugin's manifest) and the cascade, the /api/integrations* endpoints and the Web App form are all derived from that declaration — no per-plugin API or UI code.

End-to-end encryption

Optional and opt-in: set a shared passphrase in the Web App (Settings) and Phone Agent (login). Message text and recipient numbers are then AES-256-GCM encrypted in the browser/phone before they reach the API Server, which stores only ciphertext. See API Server/README.md for the wire format.

Per-surface docs

License

Apache License 2.0 — the same license Home Assistant itself uses.

The gsmnode name, wordmark and routing mark are not covered by that grant; see NOTICE and section 6 of the License. The brand image files ship inside the tree so the surfaces can render them, which is not permission to put the marks on something else.

Install Gsmnode on Unraid in a few clicks.

Find Gsmnode in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.

Open the Apps tab on your Unraid server Search Community Apps for Gsmnode Review the template variables and paths Click Install

Download Statistics

74
Total Downloads

Related apps

Details

Repository
tajniak81/gsmnode-aio:latest
Last Updated2026-07-28
First Seen2026-07-28

Runtime arguments

Web UI
http://[IP]:[PORT:80]/
Network
bridge
Shell
sh
Privileged
false

Template configuration

Web UI PortPorttcp

Host port for the Web App (the WebUI link above). Container port 80.

Target
80
Default
8090
Value
8090
PocketBase PortPorttcp

Host port for the PocketBase admin UI / API, reached at /_/. Container port 8070.

Target
8070
Default
8070
Value
8070
API Server PortPorttcp

Host port for the API Server and its embedded admin panel (served at /). Container port 8080.

Target
8080
Default
8080
Value
8080
Database (pb_data)Pathrw

PocketBase data directory — the database. Must persist across container recreates.

Target
/pb/pb_data
Default
/mnt/user/appdata/gsmnode/pb_data
Value
/mnt/user/appdata/gsmnode/pb_data
App data (/data)Pathrw

API Server state: plugin enable-state and config (plugins.json) plus settings the panel persists. Holds plugin credentials such as IMAP/SMTP logins, so back it up with the database.

Target
/data
Default
/mnt/user/appdata/gsmnode/data
Value
/mnt/user/appdata/gsmnode/data
PocketBase Admin EmailVariable

REQUIRED. PocketBase superuser, created/updated on first boot. The API Server uses this to reach the database. Distinct from the app login below.

Target
PB_ADMIN_EMAIL
PocketBase Admin PasswordVariable

REQUIRED. Password for the PocketBase superuser. Use a long, unique value.

Target
PB_ADMIN_PASSWORD
Super-Admin EmailVariable

First app user, created on boot with role superadmin if no user with this email exists. This is the account you log in to the Web App with. Leave blank to skip and create the first user by hand.

Target
GSMNODE_SUPERADMIN_EMAIL
Super-Admin PasswordVariable

Password for the app super-admin. Only used when the email above is also set. Use a long, unique value.

Target
GSMNODE_SUPERADMIN_PASSWORD
Super-Admin NameVariable

Display name for the app super-admin created above.

Target
GSMNODE_SUPERADMIN_NAME
Default
Administrator
Value
Administrator
Bootstrap SchemaVariable

Reconcile the database schema and create the super-admin on boot (idempotent). Set to false once the database is established to skip it.

Target
PB_BOOTSTRAP
Default
true
Value
true
Message TTLVariable

How long a message/call may stay unprocessed before it's marked Failed (Go duration, e.g. 5m, 30s, 1h).

Target
MESSAGE_TTL
Default
5m
Value
5m
CORS Allowed OriginsVariable

Allowed origin(s) for browsers that call the API Server directly. Match your public Web App URL, e.g. http://tower.local:8090. Comma-separate several.

Target
CORS_ALLOW_ORIGINS
Default
http://localhost:8090
Value
http://localhost:8090