All apps · 0 apps
gsmnode
Docker app from Tajniak81's Repository
Overview
Readme
View on GitHubTurn 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 Agent — controls the phone: sends/receives SMS & MMS and makes/receives calls on behalf of the gateway.
- Phone App — controls 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
![]() |
![]() |
![]() |
![]() |

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, pluginSettingsorganizations—name, pluginSettings(tenants;pluginSettingsholds the org layer of the plugin cascade)devices—device_id, name, platform, app_version, push_token, auth_token, status, last_seen_at, ownermessages—phone_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, ownerinbox—phone_number, message, type (sms/data/mms), received_at, sim_slot, data_payload, data_port, subject, attachments, encrypted, device, ownercalls—phone_number, direction, status, sim_slot, duration, started_at, device, ownerwebhooks—event, 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
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 assumeshttp://localhost:8028. Create its superuser on first launch — that is thePB_ADMIN_*login below.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.ps1Web App (
:8090):cd "Web App"; ./server/Run-WebApp.ps1Open http://localhost:8090 and sign in.
Phone Agent — see
Phone Agent/README.md(install Flutter + JDK 17,flutter create, copyandroid_overlay/,flutter run).Mirrored as its own repo at
gsmnode/phone-agent, regenerated byscripts/publish-phone-agent.sh; the process is written down inPhone Agent/PUBLISHING.md.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 byscripts/publish-phone-app.sh; the process is written down inPhone App/PUBLISHING.md.Home Assistant Plugin (optional) — install it through HACS from
gsmnode/gsmnode-ha, or copyHome Assistant Plugin/custom_components/gsmnode/into<config>/custom_components/by hand. Restart Home Assistant, then add gsmnode under Settings → Devices & Services. SeeHome 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 inHome Assistant Plugin/PUBLISHING.md.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. SeeUnraid Plugin/README.md.Published the same way, from
scripts/publish-unraid-plugin.shtogsmnode/gsmnode-unraid— Unraid installs a plugin from a URL that serves the.plgand 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) Processed → Sent → Delivered · 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
- API Server README — full endpoint reference, setup
- Web App README — dev/build, pages
- Phone App README — Flutter build, screens, API mapping
- Phone Agent README — Flutter build + native SMS wiring
- Home Assistant Plugin README — install, services, sensors, incoming events, notify entity, sidebar panel
- Unraid Plugin README — install, the notification agent's settings, what it sends
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.
Download Statistics
Related apps
Explore more like this
Explore allDetails
tajniak81/gsmnode-aio:latestRuntime arguments
- Web UI
http://[IP]:[PORT:80]/- Network
bridge- Shell
sh- Privileged
- false
Template configuration
Host port for the Web App (the WebUI link above). Container port 80.
- Target
- 80
- Default
- 8090
- Value
- 8090
Host port for the PocketBase admin UI / API, reached at /_/. Container port 8070.
- Target
- 8070
- Default
- 8070
- Value
- 8070
Host port for the API Server and its embedded admin panel (served at /). Container port 8080.
- Target
- 8080
- Default
- 8080
- Value
- 8080
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
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
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
REQUIRED. Password for the PocketBase superuser. Use a long, unique value.
- Target
- PB_ADMIN_PASSWORD
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
Password for the app super-admin. Only used when the email above is also set. Use a long, unique value.
- Target
- GSMNODE_SUPERADMIN_PASSWORD
Display name for the app super-admin created above.
- Target
- GSMNODE_SUPERADMIN_NAME
- Default
- Administrator
- Value
- Administrator
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
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
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





