The Problem

Organizations in regions where virtual phone numbers are unavailable need a programmatic way to send and receive SMS messages. Existing solutions typically require purchasing virtual numbers or rely on carrier APIs that may not be accessible globally. This project addresses that gap by using an Android phone as an SMS gateway through a simple HTTP API.

What This Does

httpsms is a full-stack system with three primary components. The api/ directory (223 files, 212 Go files) implements the HTTP service using Fiber and CockroachDB, containing the business logic, repositories, services, and telemetry. The web/ directory (124 files) provides the Nuxt/Vuetify admin interface, hosted on Firebase. The android/ directory (72 files) is the mobile application that receives HTTP commands to send/receive SMS, featuring SentReceiver.kt, ReceivedReceiver.kt, DeliveredReceiver.kt, and PhoneStateReceiver.kt — the duplicated 6-line blocks across 99 files represent a DRY violation the code health audit flagged.

Execution starts at api/cmd/fcm/main.go:14 (main), which reaches 270 functions and is called by nothing else in the repo. From there, control flows through api/pkg/di/container.go:166 (App), which is called from 15 places and wires the dependency injection container. The call graph shows WrapErrorSpan is the most-called function (164 places), followed by Start (131), Debug (117), and Info (97) — these are the telemetry wrapper functions defined in api/pkg/telemetry/zerolog_logger.go and api/pkg/telemetry/otel_tracer.go. A request traverses main -> App -> UserRepository -> DB via gorm.Open and db.Exec, touching the database through 9 resolved functions. Outbound network calls go through api/pkg/discord/client.go (29 callers), and file I/O is handled across the cache layer (api/pkg/cache/).

How It Is Wired

Execution originates at api/cmd/fcm/main.go:14 (main), which reaches 270 functions and has no callers within the repo. The dependency injection container at api/pkg/di/container.go:166 (App) is the next entry point, called from 15 places and itself reaching 31 functions. Request registration flows through api/pkg/handlers/events_handler.go:46 (Dispatch, 40 reached functions, 24 callers) and api/pkg/handlers/handler.go:18 (response helpers: responseBadRequest, responseInternalServerError, responseUnauthorized, responsePhoneAPIKeyUnauthorized, responseForbidden). The most-connected modules by import count are web/models/api, web/models/billing, web/models/heartbeat, and web/middleware/auth/web/middleware/guest — all with zero import edges and zero instability, indicating they are leaf modules with no internal dependencies. The internal call graph has 2186 resolved edges; the widest blast radius belongs to WrapErrorSpan (164 callers), Start (131), and Debug (117), all telemetry wrappers. A request path from entry to out-of-process: main -> App -> UserRepository -> DB via GORM, with filesystem and database writes. Outbound network calls go through api/pkg/discord/client.go; file I/O through the cache layer; database through 9 resolved functions.

How To Use It

Setup: Clone the repository verbatim: https://github.com/moses-y/httpsms. The project uses pnpm as package manager (evidenced by web/package.json and api/go.mod). Docker is supported via api/Dockerfile and docker-compose.yml.

Configuration: Environment variables are defined in api/.env.docker, api/.env.production, web/.env.docker, and web/.env.production — four of which contain committed secrets the audit flagged as critical. The .env.docker files are tracked despite .gitignore excluding them, meaning the live credentials are in the indexed history. Replace these with api/.env.example and web/.env.example placeholders.

Running it: Start the API server with go run api/main.go or build the Docker image. The Android app must be built and installed on the phone; the web UI runs via the standard Nuxt dev server. No Makefile or build script is present beyond the Go and Docker entry points.

Real-World Use

A developer integrates the Go client to trigger SMS from a CI pipeline:

client := httpsms.New(httpsms.WithAPIKey("sk-…"))
client.Messages.Send(context.Background(), &httpsms.MessageSendParams{
    Content: "Your OTP is 482931",
    From:    "+18005550199",
    To:      "+18005550100",
})

The API accepts the request, registers it in the CockroachDB-backed repository, and the Android phone — running the httpsms app — receives the command via Firebase Cloud Messaging, validates the API key, and sends the SMS through the device’s radio. Incoming messages are forwarded to a configured webhook endpoint. The system handles message expiration and back pressure internally.

Code Health & Issues

The static analysis produced 26 measured findings across 4 kinds:

  • HIGH/cognitive_load: Deep nesting in 15 files, max indentation depth 10 in api/docs/docs.go, api/pkg/emails/hermes_theme.go, web/pages/billing/index.vue — control flow is hard to follow. Fix: flatten with early returns/guard clauses.
  • HIGH/clarity: 300 repeated 6-line blocks across 99 files, primarily the Android receiver classes (DeliveredReceiver.kt, ReceivedReceiver.kt, SentReceiver.kt, PhoneStateReceiver.kt et al.) — duplicated logic violating DRY. Fix: extract shared helpers.
  • HIGH/cognitive_load: 8 oversized files, notably api/docs/docs.go, api/pkg/di/container.go (4612 lines), web/pages/settings/index.vue — hard to hold in one head; changes ripple widely. Fix: split into cohesive units by responsibility.
  • MEDIUM/cognitive_load: High branching density in api/pkg/repositories/gorm_message_repository.go (61 branch points over 215 lines) and api/pkg/services/email_notification_service.go — decompose decision-heavy logic.

Code health audit (11 findings, ranked by severity):

  • [CRITICAL] Rotate credentials in api/.env.docker — PUSHER_SECRET holds generated values loaded at boot, providing a working credential for a running service.
  • [HIGH] Untrack api/.env.docker though .gitignore excludes it — the file is tracked despite the ignore rule, so its credentials are the ones actually in use.
  • [HIGH] Pin third-party GitHub Actions to commit SHA — pnpm/action-setup@v4, cloudflare/pages-action@1, FirebaseExtended/action-hosting-deploy@v0 use version tags that can move.
  • [HIGH] Upgrade pinned dependencies with CVEs — github.com/xuri/excelize/v2@2.10.0 CVE-2026-54063, go.opentelemetry.io/otel@1.40.0 CVE-2026-29181, go.opentelemetry.io/otel/sdk@1.40.0 CVE-2026-39883, github.com/go-jose/go-jose/v4@4.1.3 CVE-2026-34986, plus one more.
  • [HIGH] Remove committed .env files and rotate credentials — api/.env.docker, api/.env.production, web/.env.docker, web/.env.production all contain live keys in tracked history.
  • [MEDIUM] Declare least-privilege permissions for GITHUB_TOKEN in ci.yml — no permissions declaration; token inherits repository defaults.
  • [MEDIUM] Pin container base image by digest in api/Dockerfile — golang, alpine:latest use mutable tags.
  • [MEDIUM] Gate pull requests on dependency vulnerability scan — no dependency-review-action or osv-scanner in CI.
  • [MEDIUM] Set persist-credentials: false on checkout in ci.yml — token stays in .git/config for later steps.
  • [MEDIUM] Expand test suite — 6 test files against 294 source files (ratio 0.020).

The Bottom Line

The codebase is functional and well-structured for an API-plus-mobile setup, with a clear entry point at api/cmd/fcm/main.go and a call graph that concentrates telemetry through a few wrapper functions. The Android component has significant duplication that should be refactored, and several files exceed reasonable size limits. The most urgent concern is the four committed .env files containing live credentials — these must be rotated, uncached from git, and replaced with example placeholders immediately. The dependency CVEs and unpinned GitHub Actions versions are also actionable. This project is suitable for teams that can tolerate the current code health trade-offs and are prepared to address the credential exposure as a first fix.