GOMSGGW Messaging Gateway

August 28, 2026 · View on GitHub

A high-performance multi-protocol messaging gateway bridging SMPP/MM4 (legacy clients) and REST API/Webhooks (web clients) with unified message routing, usage limits, and comprehensive logging.


Quick Start

git clone https://github.com/sagostin/gomsggw.git
cd gomsggw
cp sample.env .env
# Edit .env with your settings (see Configuration below)
./build.sh
docker-compose up -d

If you run the binary directly (e.g. ./gomsggw outside Docker), the Go binary auto-loads .env from the working directory via godotenv. Set the variables however you like (systemd unit, k8s ConfigMap, etc.) and skip the .env file.


Features

Multi-Protocol Support

ProtocolDirectionUse Case
SMPPBidirectionalZultys MX, legacy PBX
MM4BidirectionalMMS via legacy
REST APIOutboundWeb apps, Bicom PBXware
WebhooksInboundWeb apps, Bicom PBXware

Client Types

FeatureLegacyWeb
ProtocolSMPP/MM4REST/Webhook
AuthSMPP BindHTTP Basic/Bearer
Message SplittingAlwaysConfigurable
API FormatN/Ageneric, bicom, telnyx
Use CaseZultys MXBicom PBXware, Web Apps

Additional Features

  • Usage Limits - Burst/daily/monthly quotas (SMS & MMS) with timezone support
  • Number Organization - Tags and groups for multi-tenant deployments
  • Per-Number Auto-Reply - Configurable auto-response on inbound (SMS + MMS) with STOP respect and per-texter cooldown
  • MMS Transcoding - Automatic media optimization for carrier limits
  • Enhanced Logging - Client types, delivery methods, segment tracking
  • Global Retry Config - Configurable retries for webhooks, SMPP, MM4

Integration Examples

Zultys MX (SMPP/Legacy)

# Create legacy client (address required for IP ACL)
curl -X POST http://gateway:3000/clients \
  -H "Authorization: Basic $(echo -n 'admin:API_KEY' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"username":"zultys","password":"smpp_pass","type":"legacy","name":"Zultys MX","address":"192.168.1.100"}'

# Add number
curl -X POST http://gateway:3000/clients/1/numbers \
  -H "Authorization: Basic $(echo -n 'admin:API_KEY' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"number":"+12505551234","carrier":"telnyx"}'

Zultys SMPP Settings: Host: gateway-ip, Port: 9550, System ID: zultys, Bind: Transceiver

Bicom PBXware (REST API)

# Create web client
curl -X POST http://gateway:3000/clients \
  -H "Authorization: Basic $(echo -n 'admin:API_KEY' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"username":"bicom","password":"api_key","type":"web","name":"Bicom PBXware","timezone":"America/Vancouver"}'

# Configure Bicom format and webhook
curl -X PUT http://gateway:3000/clients/2/settings \
  -H "Authorization: Basic $(echo -n 'admin:API_KEY' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"api_format":"bicom","default_webhook":"https://bicom.local/smsservice/connector"}'

# Send SMS
curl -X POST http://gateway:3000/messages/send \
  -H "Authorization: Bearer $(echo -n 'bicom:api_key' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"from":"+12505551234","to":"+14155559876","text":"Hello!"}'

Per-Number Auto-Reply (Bounce)

Configure a specific number to auto-respond on inbound — and suppress the inbound from normal client delivery. Classic use case: a number that "doesn't accept text messages, please call us instead."

# 1. Master switch in .env
AUTO_REPLY_ENABLED=true
AUTO_REPLY_DEFAULT_MESSAGE="This number does not accept text messages. Please call us instead."

# 2. Find the number's DB id (note "id" in each entry's "numbers")
curl -u admin:$API_KEY http://gateway:3000/clients/7 | jq '.numbers[] | {id, number, settings}'

# 3. Enable + customize on the specific number
curl -X PUT http://gateway:3000/numbers/42/auto-reply \
  -u admin:$API_KEY -H "Content-Type: application/json" \
  -d '{"enabled":true,"message":"Please call 250-555-0100 — we do not accept texts.","cooldown_secs":60}'

# 4. Verify (effective_* reflects master × per-number × STOP merge)
curl -u admin:$API_KEY http://gateway:3000/numbers/42/auto-reply

Or use the interactive Python manager (scripts/main.py) — menu items j to show and k to configure. The q quick-flow also prompts to set auto-reply on every newly added number.

See docs/number_management.md for full behavior, STOP semantics, and cooldown details.


API Endpoints

The full request/response shapes live in docs/api_reference.md. The tables below are an at-a-glance index of every route the gateway exposes.

Admin Endpoints (API_KEY auth)

MethodEndpointDescription
GET/healthHealth check (no auth)
GET/statsConnection stats
GET/clientsList all clients
POST/clientsCreate client
DELETE/clients/{id}Delete a client
PATCH/clients/{id}/passwordUpdate client password
GET/clients/{id}/numbersList client numbers
POST/clients/{id}/numbersAdd number to client
PUT/clients/{id}/numbers/{number_id}Update number
DELETE/clients/{id}/numbers/{number_id}Remove number
GET/clients/{id}/settingsGet client settings
PUT/clients/{id}/settingsUpdate client settings
GET/clients/{id}/failoversList client failovers
POST/clients/{id}/failoversAdd failover
PUT/clients/{id}/failovers/{failover_id}Update failover
DELETE/clients/{id}/failovers/{failover_id}Remove failover
GET/clients/{id}/legacy-statusLegacy (SMPP + MM4) session status + failovers
POST/clients/{id}/api-keysCreate tenant API key
GET/clients/{id}/api-keysList tenant API keys
DELETE/clients/{id}/api-keys/{key_id}Revoke tenant API key
GET/numbers/{id}/settingsGet number settings
PUT/numbers/{id}/settingsUpdate number settings
GET/numbers/{id}/auto-replyGet resolved auto-reply config
PUT/numbers/{id}/auto-replyUpdate auto-reply config (creates settings lazily)
GET/carriersList carriers
POST/carriersAdd carrier
POST/clients/reloadReload clients from DB
POST/carriers/reloadReload carriers
POST/inbound/{carrier}Carrier inbound webhook (Telnyx/Twilio/OVP)

Web Client Endpoints (client auth or API key)

MethodEndpointDescription
POST/messages/sendSend SMS/MMS
POST/messagesAlias of /messages/send (Bicom compatibility)
GET/messages/usageCheck quota usage
GET/messages/historyPaginated message history
POST/messages/batchSubmit a batch job
POST/messages/batch/checkPre-check batch against limits
GET/messages/batchList recent batch jobs
GET/messages/batch/{id}Get batch job status
GET/messages/batch/{id}/messagesList messages in a batch
POST/messages/batch/{id}/cancelCancel a batch job
DELETE/messages/batch/{id}/messages/{msg_id}Cancel a single batch message

Media (public)

MethodEndpointDescription
GET/media/{token}Retrieve MMS media by UUID access token

Configuration

Required Environment Variables

# Security (REQUIRED)
ENCRYPTION_KEY=your-32-character-key-here
API_KEY=your-admin-api-key

# Server Ports
WEB_LISTEN=0.0.0.0:3000
SMPP_LISTEN=0.0.0.0:9550
MM4_LISTEN=0.0.0.0:2566

# PostgreSQL
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_USER=smsgw
POSTGRES_PASSWORD=your-secure-password
POSTGRES_DB=smsgw

Global Retry Configuration

WEBHOOK_RETRIES=3
WEBHOOK_TIMEOUT_SECS=10
SMPP_RETRIES=3
SMPP_TIMEOUT_SECS=30
MM4_RETRIES=3
MM4_TIMEOUT_SECS=60
NOTIFY_SENDER_ON_FAILURE=true

See sample.env for all available options.


CLI Management Tool

cd scripts
pip install requests
export MSGGW_BASE_URL=http://localhost:3000
export MSGGW_API_KEY=your-api-key
python main.py

Interactive menu for managing carriers, clients, and numbers. See scripts/README.md.


Admin Control Panel (Web UI)

A Vue 3 + Vite admin portal lives in admin-ui/ and covers the same operations as the Python CLI — carriers, clients, numbers (bulk add, auto-reply), API keys, failovers, and SMPP session status. You only need the gateway URL and the API_KEY master key.

Alongside the gateway (same compose stack): the root docker-compose.yml includes the admin-ui service — docker compose up -d --build brings it up at http://<host>:8080/ui/ with the API proxied in-stack.

Standalone: docker compose -f admin-ui/docker-compose.yml up -d --build (attaches to the shared gomsggw-network).

Behind Caddy with TLS: see Caddyfile.example for either fronting the container or serving the static build directly.

If you host the panel on a different origin entirely, the gateway allows cross-origin browser calls by default (CORS_ALLOWED_ORIGINS=*, see configuration). See admin-ui/README.md.


Testing

Unit tests cover the pure-function surface of the gateway (encryption, rate-limit resolution, SMPP conversation ordering, batch template rendering, CSV parsing, MMS URL helpers, GSM-7 validation, etc.). They run with no external dependencies — no PostgreSQL or network required.

make test            # quick run with -race
make test-verbose    # see every subtest

The Makefile runs the root package only; migration/ has a pre-existing duplicate-main build conflict and scripts/ contains an unrelated main, so both are excluded.


Documentation

DocumentDescription
ArchitectureSystem design and message flow
API ReferenceComplete endpoint documentation
DeploymentDocker setup & production config
ConfigurationAll environment variables
Data ModelsDatabase schemas
Web ClientsREST API / Bicom integration
Legacy ClientsSMPP / Zultys integration
Usage LimitsQuota management
Number ManagementTags, groups, per-number settings
TranscodingMMS media optimization
MigrationDatabase migration guide

Security

  • Passwords encrypted at rest using AES-256
  • Admin endpoints protected by API_KEY
  • Client endpoints use Basic/Bearer auth
  • SMPP ACL validates source IP for legacy clients
  • CORS for browser callers defaults to any origin (CORS_ALLOWED_ORIGINS=*) — the API_KEY still gates access; restrict it when exposing the admin API to the internet
  • Usernames stored in plaintext (used as lookup key)

License

See LICENSE.md file.