The Sandbox for SMS.
A modern, self-hosted SMS sandbox for local development, testing, and CI/CD. Capture, inspect, search, replay, and debug SMS messages without sending real SMS to mobile networks.
Status: v1.0.0 released. SMS capture/search/replay, the dashboard, API-key auth (with rotation), provider-compatible endpoints, multi-tenancy (organizations/teams, per-org rate limiting), templates, export, AI detection/classification/spam/test-data, Kubernetes manifests and a Helm chart, OpenTelemetry tracing, Prometheus metrics, Grafana dashboards, 4 native SDKs (PHP/Go/Node.js/Python), a full OpenAPI reference, and an extended CI/CD pipeline are all live — see the changelog for the version-by-version detail. SDK package registry publishing (Packagist/pkg.go.dev/npm/PyPI) is the one remaining gap — see docs/sdks.md. Follow progress in checklist.md. Per-service stack and feature docs live in docs/.
- Why SMSPit?
- Features
- Screenshots
- Architecture
- Tech Stack
- Project Structure
- Quick Start
- Example Usage
- REST API
- Dashboard Features
- AI Features
- Provider Compatibility
- Observability
- Roadmap
- SDKs
- Contributing
- License
- Inspiration
- Support
Testing SMS integrations usually requires:
- Real phone numbers
- Paid SMS providers
- Network connectivity
- Provider credentials
- Manual verification
SMSPit eliminates all of these.
Simply point your application to SMSPit instead of a real SMS provider, and every outgoing SMS is captured in a beautiful web dashboard.
No SMS is actually delivered.
All shipped — see the Roadmap for which release introduced each.
- 📩 Capture outgoing SMS
- 🔍 Powerful search and filtering
- ⚡ Real-time dashboard
- 🔄 Replay SMS requests
- 📱 OTP detection, classification, and spam detection (AI-powered)
- 📦 REST API, fully documented via OpenAPI
- 🔑 API Key authentication, rotation, and revocation
- 📊 Delivery statistics
- 📁 Export messages (CSV/JSON)
- 🧪 CI/CD friendly
- 🐳 Docker support, Kubernetes manifests, and a Helm chart
- 🌐 Multi-tenant (organizations, teams, per-org rate limiting)
- 📡 WebSocket live updates
- 📜 Request history
- 🔥 Provider emulation (Vonage, AWS SNS, MessageBird)
- ⚙️ OpenAPI documentation
- 📈 Prometheus metrics, Grafana dashboards, and OpenTelemetry tracing
- 🧰 Native SDKs (PHP, Go, Node.js, Python)
The inbox — captured messages with AI-detected OTP, category, and spam badges:
+----------------------+
| API Gateway (Go) |
+----------+-----------+
|
+----------------+----------------+
| |
+---------v------+ +--------v-------+
| Auth Service | | SMS Service |
| Laravel | | Node.js |
+----------------+ +--------+-------+
| \
| \ (sync, on capture)
Redis Streams +--v------------+
| | AI Service |
+--------v--------+ | Python |
| Worker (Go) |-+--------------+
+-----------------+ ^
(async, classify only, |
no write-back) -----------+
PostgreSQL: shared instance, schema owned by Auth Service
+----------------------+
| React Dashboard |
| (REST + WebSocket) |
+----------------------+
The gateway only reverse-proxies to auth-service and sms-service — ai-service is never proxied; it's called directly by sms-service (synchronously, on capture) and worker (asynchronously, off the queue). See docs/architecture.md for the full request flow.
| Component | Technology |
|---|---|
| API Gateway | Go |
| Authentication | Laravel |
| SMS Service | NestJS |
| AI Service | FastAPI |
| Queue | Redis / NATS / Kafka |
| Database | PostgreSQL |
| Cache | Redis |
| Dashboard | React |
| Container | Docker |
| Monitoring | Prometheus + Grafana |
| Tracing | OpenTelemetry |
All service folders below are fully implemented, not just scaffolded — see checklist.md for the full build history.
SMSPit/
├── gateway/ # API Gateway (Go)
│ ├── cmd/
│ │ └── gateway/
│ │ └── main.go
│ ├── internal/
│ │ ├── router/
│ │ ├── middleware/
│ │ └── proxy/
│ ├── config/
│ ├── Dockerfile
│ └── go.mod
│
├── auth-service/ # Authentication & API Keys (Laravel)
│ ├── app/
│ │ ├── Http/Controllers/
│ │ ├── Models/
│ │ └── Services/
│ ├── routes/
│ ├── database/migrations/
│ ├── tests/
│ ├── Dockerfile
│ └── composer.json
│
├── sms-service/ # SMS Capture & Replay (NestJS)
│ ├── src/
│ │ ├── messages/
│ │ ├── websocket/
│ │ ├── providers/
│ │ └── main.ts
│ ├── test/
│ ├── Dockerfile
│ └── package.json
│
├── ai-service/ # OTP/Spam Detection, Classification (FastAPI)
│ ├── app/
│ │ ├── routers/
│ │ ├── schemas/
│ │ ├── services/
│ │ ├── config.py
│ │ └── main.py
│ ├── tests/
│ ├── Dockerfile
│ └── requirements.txt
│
├── worker/ # Background Jobs / Queue Consumers (Go)
│ ├── cmd/
│ │ └── worker/
│ │ └── main.go
│ ├── internal/
│ │ ├── consumer/
│ │ ├── queue/
│ │ └── aiclient/
│ ├── config/
│ ├── Dockerfile
│ └── go.mod
│
├── dashboard/ # Web Dashboard (React)
│ ├── src/
│ │ ├── components/
│ │ ├── pages/
│ │ ├── hooks/
│ │ ├── api/
│ │ └── App.tsx
│ ├── public/
│ ├── Dockerfile
│ └── package.json
│
├── proto/ # Shared gRPC/protobuf definitions
│ └── sms/v1/
│
├── docs/ # Documentation & diagrams
│ ├── images/
│ ├── api/
│ └── architecture.md
│
├── docker/ # Shared Dockerfiles / compose fragments
│ └── base/
│
├── deployments/ # Kubernetes, Helm charts, environment configs
│ ├── k8s/
│ └── helm/
│
├── scripts/ # Dev & CI helper scripts, plus load-test/
│ ├── setup.sh
│ ├── dev-up.sh
│ └── load-test/
│
├── sdks/ # Native client SDKs
│ ├── php/
│ ├── go/
│ ├── nodejs/
│ └── python/
│
├── docker-compose.yml # wires gateway + auth-service + sms-service + ai-service + worker + dashboard + Postgres + Redis + Jaeger + Prometheus + Grafana
├── checklist.md # Build checklist
├── CLAUDE.md # AI agent working guide
├── LICENSE
└── README.md
docker-compose.yml wires up all 8 services (gateway, auth-service, sms-service, ai-service, worker, dashboard, Postgres, Redis) plus the observability stack (Jaeger, Prometheus, Grafana). docker compose up -d itself hasn't been run/verified in the environment this was built in (no Docker available during development) — see CHANGELOG.md and docs/production-deployment.md for what was verified instead, and re-verify against a real docker compose up -d before depending on it. scripts/dev-up.sh is the Docker-free alternative used throughout this project's own development.
All API requests require an API key: generate one via POST /api/api-keys on auth-service (or the dashboard's own /api-keys page, which needs no key itself to get in) and pass it as Authorization: Bearer <key> against the gateway.
git clone https://github.com/jmrashed/SMSPit.git
cd SMSPitdocker compose up -dOpen
Dashboard
http://localhost:5173
API
http://localhost:8080
The idea: point your app at SMSPit instead of your real SMS provider. No code change beyond configuration — every message gets captured, not delivered.
$provider = new SmsClient($apiKey);
$provider->messages->send(
"+8801700000000",
[
"from" => "SMSPit",
"body" => "Your OTP is 845231",
]
);Swap your provider's base URL for your local SMSPit instance (works out of the box if your SDK lets you override the base URL, or via a compatible adapter for providers SMSPit emulates):
$provider = new SmsClient($apiKey, [
"baseUri" => "http://localhost:8080",
]);Or call the native SMSPit REST API directly from any language:
curl -X POST http://localhost:8080/api/v1/messages \
-H "Authorization: Bearer $SMSPIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+8801700000000",
"from": "SMSPit",
"message": "Your OTP is 845231"
}'{
"id": "sms_123456",
"status": "captured"
}The message appears instantly in the dashboard — no network call leaves your machine, and no real SMS is sent.
Every endpoint below requires Authorization: Bearer <api key> (validated against auth-service at the gateway, and again at sms-service itself for defense in depth), except the provider-compatible adapters and the API key bootstrap/management routes, which are intentionally unauthenticated (see docs/security.md). This is a summary — the full, versioned contract (request/response schemas, every status code) is in docs/openapi/openapi.yaml, also viewable via docs/openapi/site/index.html.
POST /api/v1/messages{
"to": "+8801700000000",
"from": "SMSPit",
"message": "Your OTP is 845231"
}Response
{
"id":"sms_123456",
"status":"captured"
}GET /api/v1/messages
GET /api/v1/messages/{id}
DELETE /api/v1/messages
POST /api/v1/messages/{id}/replay
GET /api/v1/statistics
GET /api/v1/messages/export?format=csv
GET /api/v1/messages/export?format=json
Same filters as List Messages (to, from, created_after, created_before); streamed as an attachment, so a Content-Disposition header carries the suggested filename.
GET /api/v1/templates
POST /api/v1/templates
GET /api/v1/templates/{id}
PUT /api/v1/templates/{id}
DELETE /api/v1/templates/{id}
Templates support {{variable}} placeholders, filled in at send time.
GET /api/organizations
POST /api/organizations
GET /api/organizations/{id}
PUT /api/organizations/{id}
DELETE /api/organizations/{id}
GET /api/organizations/{id}/teams
POST /api/organizations/{id}/teams
POST /api/organizations/{id}/teams/{team}/members
DELETE /api/organizations/{id}/teams/{team}/members/{user}
Served by auth-service (paths above as seen directly on auth-service; through the gateway, prefix with /auth instead of /api — e.g. POST /auth/organizations). An API key scoped to an organization only sees that organization's messages, keys, and templates; ungrouped keys (no organization) see only ungrouped data — organization membership is a partition, not a wildcard.
- Inbox
- Search
- Filters
- Raw Request
- Headers
- Replay
- Export
- API Logs
- Timeline
- WebSocket Updates
- Organization/Team Switcher
- Template Picker
- OTP Badge & Copy-to-Clipboard
- Classification Tags
- Spam Flag & Manual Override
- Generate Test Data
ai-service (FastAPI) provides OTP detection, message classification, spam detection, and synthetic test-data generation. sms-service calls it synchronously (with a short timeout) on every message capture — AI enrichment never blocks or fails a capture, and degrades to "not detected" if ai-service is unreachable. worker (Go) additionally consumes a Redis Streams queue (sms.messages.created, published by sms-service on capture) for its own async workloads on top of ai-service — see docs/redis.md for the full architecture.
| Endpoint | Purpose |
|---|---|
POST /detect-otp |
Regex-based OTP extraction |
POST /classify |
Rule-based category: otp / transactional / marketing / other |
POST /detect-spam |
Keyword/heuristic spam scoring (is_spam, score) |
POST /generate-test-data |
Synthetic SMS samples (count, type), for exercising the dashboard/API without a real integration |
Captured messages carry otp, category, and is_spam fields (see REST API); the dashboard surfaces these as a copyable OTP badge, a classification tag, and a spam flag with a manual "mark as not spam" override.
SMSPit exposes drop-in-compatible endpoints for popular SMS providers, so an application can point its existing SDK at SMSPit by swapping the base URL — no other code changes. See docs/api/provider-compatibility.md for the full path convention and field mappings.
Shipped (v0.3):
- Vonage —
POST /providers/vonage/sms/json - AWS SNS —
POST /providers/sns - MessageBird —
POST /providers/messagebird/messages
Planned:
- Infobip
- Plivo
- Clickatell
These endpoints are unauthenticated by design, matching the "swap the base URL, nothing else" premise — they don't sit behind /api/v1 and aren't covered by API-key auth.
Every service exposes Prometheus metrics (/metrics) and emits OpenTelemetry traces, wired into docker-compose.yml alongside Jaeger, Prometheus, and pre-provisioned Grafana dashboards (request rate/latency/error rate, message volume, OTP detection rate). See docs/observability.md for the full setup and how it was verified in an environment without Docker.
- SMS Capture
- Dashboard
- Search
- REST API
- Docker
- Authentication
- API Keys
- Replay
- Statistics
- WebSocket
- Provider Emulation
- Teams
- Organizations
- Message Templates
- Export
- AI OTP Detection
- AI Classification
- AI Spam Detection
- AI Test Data Generator
- Kubernetes manifests + Helm chart
- OpenTelemetry tracing
- Prometheus metrics + Grafana dashboards
- Hardened multi-tenancy (org-scoping audit, per-org rate limiting)
- Security review (secrets management, API key rotation, input validation)
- Load testing (found and fixed a real concurrency bottleneck — see docs/load-testing.md)
- Native SDKs (PHP, Go, Node.js, Python)
- Full OpenAPI reference + Swagger UI docs site
- Extended CI/CD (every service tested on every PR, image publishing, staging deploy)
- Production deployment guide
- End-to-end QA pass (found and fixed a real routing bug — see docs/qa-day98.md)
SDK package registry publishing (Packagist/pkg.go.dev/npm/PyPI) is the one remaining gap — see docs/sdks.md.
- PHP, Go, Node.js, Python — built, see docs/sdks.md and each SDK's own README under sdks/. Not yet published to a package registry.
- Java, .NET — planned, not yet started.
Contributions are welcome — see CONTRIBUTING.md for setup, testing, and PR conventions. checklist.md tracks build history and what's still open, and CLAUDE.md documents this repo's full working conventions.
Released under the MIT License.
SMSPit is inspired by developer tools that make local development faster and more enjoyable, such as:
- Mailpit
- MailHog
- MockServer
- WireMock
- LocalStack
If you find SMSPit useful, please consider:
⭐ Star the repository
🐛 Report bugs
💡 Suggest new features
🤝 Contribute to the project
Made with ❤️ for developers.
