From 798162ae4f55fc2211e903ccd32a902680ec5f17 Mon Sep 17 00:00:00 2001 From: Min Zeya Phyo Date: Sat, 1 Aug 2026 00:27:32 +0800 Subject: [PATCH 1/2] welcome page and documentatino --- config/routes.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 5347c9c..e758f6c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -72,6 +72,7 @@ Rails.application.routes.draw do end end - # Root route - root to: proc { [200, {}, ["SMS Gateway API v1.0"]] } + # Public pages + get "docs", to: "pages#docs" + root to: "pages#home" end From a5ade4006ddd41daa43b7048be8a0123039613f7 Mon Sep 17 00:00:00 2001 From: Min Zeya Phyo Date: Sat, 1 Aug 2026 00:27:37 +0800 Subject: [PATCH 2/2] welcome page and documentatino --- app/controllers/pages_controller.rb | 15 + app/views/layouts/public.html.erb | 273 ++++++++++++ app/views/pages/docs.html.erb | 655 ++++++++++++++++++++++++++++ app/views/pages/home.html.erb | 242 ++++++++++ 4 files changed, 1185 insertions(+) create mode 100644 app/controllers/pages_controller.rb create mode 100644 app/views/layouts/public.html.erb create mode 100644 app/views/pages/docs.html.erb create mode 100644 app/views/pages/home.html.erb diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb new file mode 100644 index 0000000..2c6bf00 --- /dev/null +++ b/app/controllers/pages_controller.rb @@ -0,0 +1,15 @@ +class PagesController < ActionController::Base + layout "public" + protect_from_forgery with: :exception + + # GET / + # Public landing page introducing the SMS Gateway API. + def home + end + + # GET /docs + # Interactive API documentation covering every endpoint, the WebSocket + # protocol, webhooks, authentication, rate limits, and error codes. + def docs + end +end diff --git a/app/views/layouts/public.html.erb b/app/views/layouts/public.html.erb new file mode 100644 index 0000000..5be8de7 --- /dev/null +++ b/app/views/layouts/public.html.erb @@ -0,0 +1,273 @@ + + + + + + <%= content_for(:title) || "MySMSAPio — SMS Gateway API" %> + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %> + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + + + + + + + + + + + + + +
+ +
+ +
+ <%= yield %> +
+ + +
+
+
+
+
+ +
+
+

MySMSAPio

+

SMS Gateway API

+
+
+
+ <%= link_to "Home", root_path, class: "hover:text-sky-400 transition-colors" %> + <%= link_to "API Docs", docs_path, class: "hover:text-sky-400 transition-colors" %> + <%= link_to "Admin", admin_login_path, class: "hover:text-sky-400 transition-colors" %> + · + Rails 8 · Ruby 3.4 +
+
+

© <%= Time.current.year %> MySMSAPio. Built with Rails 8, Sidekiq, Action Cable & Redis.

+
+
+ + + + diff --git a/app/views/pages/docs.html.erb b/app/views/pages/docs.html.erb new file mode 100644 index 0000000..3fa8a13 --- /dev/null +++ b/app/views/pages/docs.html.erb @@ -0,0 +1,655 @@ + +
+ + + + + +
+ + +
+

Reference

+

API Documentation

+

Every endpoint, parameter, and response for the MySMSAPio SMS Gateway. All routes are prefixed with /api/v1.

+
+ + +
+

Getting Started

+
+

The MySMSAPio API is a JSON REST API. There are two classes of consumer:

+
    +
  • Client applications — send SMS, generate/verify OTPs, read inbound messages, and query admin stats. Authenticated with a api_live_… key.
  • +
  • Gateway devices — Android phones that register, send heartbeats, report inbound SMS and delivery receipts. Authenticated with a gw_live_… key.
  • +
+
+ +
+
+

Base URL

+ https://your-host/api/v1 +
+
+

Content-Type

+ application/json +
+
+
+ + +
+

Authentication

+

All authenticated endpoints expect an API key in the Authorization header as a Bearer token. Keys are SHA-256 hashed at rest — the raw key is only shown once at creation.

+ +
+
+
+ + Client key +
+ api_live_<64 hex chars> +

Permissions-based (send_sms, receive_sms…). Can expire.

+
+
+
+ + Gateway key +
+ gw_live_<64 hex chars> +

Bound to a single gateway device. Must be active.

+
+
+ +
+# Bearer token in the Authorization header
+curl https://your-host/api/v1/sms/received \
+  -H "Authorization: Bearer api_live_..." +
+
+ + +
+

Rate Limits

+

Limits are enforced via Redis and return 429 Too Many Requests with a retry_after hint.

+
+ + + + + + + + + + + + + +
ResourceLimitWindow
Send SMS100per minute / API key
Send OTP3per hour / phone number
OTP verify attempts3per code
+
+
+ + +
+

Error Codes

+

Errors are returned as JSON with an error field. Validation errors also include details.

+
+ + + + + + + + + <% errors = [ + ["400", "Bad Request — missing required parameter"], + ["401", "Unauthorized — missing or invalid API key"], + ["403", "Forbidden — insufficient permissions"], + ["404", "Not Found — resource does not exist"], + ["409", "Conflict — e.g. gateway already registered"], + ["422", "Unprocessable Entity — validation failure"], + ["429", "Too Many Requests — rate limit exceeded"], + ["500", "Internal Server Error"] + ] %> + <% errors.each do |code, meaning| %> + + + + + <% end %> + +
StatusMeaning
<%= code %><%= meaning %>
+
+
+ + +
+ + Client Application API + +
+ + + <%# Reusable endpoint block. Data derived from the controller source. %> +
+
+ POST + /sms/send +
+

Send an SMS

+

Queues an outbound message. The API returns immediately with a message_id; delivery happens asynchronously via an available gateway. Rate limited to 100/min per API key.

+ +

Request body

+
+ + + + + + + + + + + +
ParameterTypeRequiredDescription
tostringyesE.164 phone number (validated via Phonelib)
messagestringyesMessage body (max 1600 chars)
+
+ +

Example request

+
+curl -X POST https://your-host/api/v1/sms/send \
+  -H "Authorization: Bearer api_live_..." \
+  -H "Content-Type: application/json" \
+  -d '{"to":"+14155551234","message":"Your code is 482915"}' +
+ +

Response · 202 Accepted

+
+{
+  "success": true,
+  "message_id": "msg_3f8a9c1b2e7d4f60",
+  "status": "queued"
+} +
+
+ + +
+
+ GET + /sms/status/:message_id +
+

Check SMS Status

+

Returns the current lifecycle state of a message, including timestamps.

+ +

Path parameters

+
+ + + + + + + + + +
ParameterTypeDescription
message_idstringThe ID returned when the message was created
+
+ +

Response · 200 OK

+
+{
+  "message_id": "msg_3f8a9c1b2e7d4f60",
+  "status": "delivered",
+  "sent_at": "2026-08-01T12:00:00Z",
+  "delivered_at": "2026-08-01T12:00:03Z",
+  "failed_at": null,
+  "error_message": null
+} +
+

Statuses: queuedpendingsentdelivered | failed

+
+ + +
+
+ GET + /sms/received +
+

List Received SMS

+

Paginated list of inbound messages, newest first. Optional filters by phone number or date.

+ +

Query parameters

+
+ + + + + + + + + + + + +
ParameterTypeDefaultDescription
phone_numberstringFilter by sender phone
sincedatetimeOnly messages after this time
limitinteger50Items per page
+
+ +

Response · 200 OK

+
+{
+  "messages": [
+    {
+      "message_id": "msg_7a2f...",
+      "from": "+14155559999",
+      "message": "STOP",
+      "received_at": "2026-08-01T12:05:00Z"
+    }
+  ],
+  "total": 1,
+  "page": 1,
+  "pages": 1
+} +
+
+ + +
+
+ POST + /otp/send +
+

Send an OTP

+

Generates a 6-digit code, sends it via SMS, and returns the expiry time. Max 3 codes per phone per hour.

+ +

Request body

+
+ + + + + + + + + + + + +
ParameterTypeRequiredDefault
phone_numberstringyes
purposestringnoauthentication
expiry_minutesintegerno5
+
+ +

Response · 200 OK

+
+{
+  "success": true,
+  "expires_at": "2026-08-01T12:10:00Z",
+  "message_id": "msg_9b1c..."
+} +
+
+ + +
+
+ POST + /otp/verify +
+

Verify an OTP

+

Validates the 6-digit code. After 3 failed attempts the code is expired. On success the code is marked verified and cannot be reused.

+ +

Request body

+
+ + + + + + + + + + +
ParameterTypeRequired
phone_numberstringyes
codestringyes
+
+ +
+
+

Success · 200

+
+{
+  "success": true,
+  "verified": true
+} +
+
+
+

Failure · 200

+
+{
+  "success": false,
+  "verified": false,
+  "error": "Invalid or expired OTP",
+  "attempts_remaining": 2
+} +
+
+
+
+ + +
+
+ GET + /admin/stats +
+

Admin Statistics

+

Aggregate system metrics: gateway counts, message throughput, pending/failed counts, and OTP performance for today.

+ +

Response · 200 OK

+
+{
+  "gateways": { "total": 4, "active": 3, "online": 2, "offline": 2 },
+  "messages": {
+    "total_sent": 10234, "total_received": 8821,
+    "sent_today": 142, "received_today": 98,
+    "total_today": 240, "pending": 3, "failed_today": 1
+  },
+  "otp": { "sent_today": 56, "verified_today": 49, "verification_rate": 87.5 },
+  "timestamp": "2026-08-01T12:00:00Z"
+} +
+
+ + +
+
+ GET + /admin/gateways +
+

List Gateways

+

Returns every registered gateway device with live status and counters.

+ +
+ POST + /admin/gateways/:id/toggle +
+

Activate or deactivate a gateway. Toggling off stops it from receiving outbound commands.

+ +

Toggle response · 200 OK

+
+{
+  "success": true,
+  "gateway": { "id": 1, "device_id": "pixel-001", "active": false }
+} +
+
+ + +
+ + Gateway Device API + +
+ + +
+
+ POST + /gateway/register +
+

Register a Gateway

+

No auth   Registers an Android device and returns a fresh gateway API key. Returns 409 if the device_id already exists.

+ +

Request body

+
+ + + + + + + + + + + +
ParameterTypeRequiredDescription
device_idstringyesUnique device identifier
namestringnoFriendly name (defaults to device id prefix)
+
+ +

Response · 201 Created

+
+{
+  "success": true,
+  "api_key": "gw_live_a1b2c3...",
+  "device_id": "pixel-001",
+  "websocket_url": "wss://your-host/cable"
+} +
+

Save api_key immediately — it is never shown again.

+
+ + +
+
+ POST + /gateway/heartbeat +
+

Gateway Heartbeat

+

Refreshes the gateway's online status. A gateway is marked offline if no heartbeat arrives within 2 minutes (handled by CheckGatewayHealthJob).

+ +

Optional body

+
+ + + + + + + + + + + +
ParameterTypeDescription
battery_levelinteger0–100
signal_strengthintegerdBm
messages_in_queueintegerPending outbound count on device
+
+ +

Response · 200 OK

+
+{ "success": true, "pending_messages": 3 } +
+
+ + +
+
+ POST + /gateway/sms/received +
+

Report Inbound SMS

+

Called by the gateway when a new SMS arrives on the device. Creates an inbound record and triggers ProcessInboundSmsJob (which fires webhooks).

+ +

Request body

+
+ + + + + + + + + + + +
ParameterTypeRequired
senderstringyes
messagestringyes
timestampdatetimeno
+
+ +

Response · 200 OK

+
+{ "success": true, "message_id": "msg_7a2f..." } +
+
+ + +
+
+ POST + /gateway/sms/status +
+

Report Delivery Status

+

Updates an outbound message's status. On failed, RetryFailedSmsJob is enqueued if retries remain (< 3).

+ +

Request body

+
+ + + + + + + + + + + + +
ParameterTypeRequiredValues
message_idstringyes
statusstringyessent · delivered · failed
error_messagestringnoReason for failure
+
+ +

Response · 200 OK

+
+{ "success": true } +
+
+ + +
+ + Realtime + +
+ + +
+
+ WS + /cable +
+

WebSocket Channel

+

Gateways maintain a persistent Action Cable connection for real-time command dispatch and status reporting. Connections authenticate with the gateway API key as a query param.

+ +

Connect

+
+wscat -c "wss://your-host/cable?api_key=gw_live_..." +
+ +

Server → Gateway commands

+
+{
+  "action": "send_sms",
+  "message_id": "msg_3f8a...",
+  "recipient": "+14155551234",
+  "message": "Hello!"
+} +
+ +

Gateway → Server events

+
+ + + + + + + + + + +
actionPayload
heartbeatbattery_level, signal_strength, messages_in_queue
delivery_reportmessage_id, status, error_message
message_receivedsender, message, timestamp
+
+
+ + +
+
+ WEBHOOK + Outbound HTTP POST +
+

Webhooks

+

When configured, the API delivers signed event payloads to your URL via TriggerWebhookJob. If a secret key is set, each request is signed with HMAC-SHA256.

+ +
+ <% events = [ + { event: "sms_received", icon: "fa-arrow-down", color: "text-emerald-400", desc: "Inbound SMS arrived" }, + { event: "sms_sent", icon: "fa-paper-plane", color: "text-sky-400", desc: "Message dispatched to gateway" }, + { event: "sms_failed", icon: "fa-circle-xmark", color: "text-red-400", desc: "Delivery failed" } + ] %> + <% events.each do |e| %> +
+ + <%= e[:event] %> +

<%= e[:desc] %>

+
+ <% end %> +
+ +

Example payload (sms_received)

+
+{
+  "event": "sms_received",
+  "message_id": "msg_7a2f...",
+  "from": "+14155559999",
+  "message": "Confirm",
+  "received_at": "2026-08-01T12:05:00Z"
+} +
+ +

Verifying the signature

+
+# Ruby
+signature = OpenSSL::HMAC.hexdigest("SHA256", secret_key, payload.to_json)
+# Compare with the X-Webhook-Signature header +
+
+ + + +
+
diff --git a/app/views/pages/home.html.erb b/app/views/pages/home.html.erb new file mode 100644 index 0000000..07e6cd0 --- /dev/null +++ b/app/views/pages/home.html.erb @@ -0,0 +1,242 @@ + +
+
+ +
+ + + + + SMS Gateway · API v1 +
+ + +
+ + + +
+ +
+
+ +

+ Send SMS through your Android devices +

+ +

+ A production-grade REST & WebSocket API that turns any Android phone into a + programmable SMS gateway. Send messages, generate OTPs, receive inbound texts, + and orchestrate fleets of devices in real time. +

+ +
+ <%= link_to docs_path, class: "btn-accent inline-flex items-center gap-2 rounded-xl px-7 py-3.5 text-base font-semibold text-slate-950" do %> + Explore the API + <% end %> + <%= link_to admin_login_path, class: "btn-ghost inline-flex items-center gap-2 rounded-xl px-7 py-3.5 text-base font-semibold text-slate-200" do %> + Admin Console + <% end %> +
+ + +
+ Base URL + https://<your-host>/api/v1 + +
+
+
+ + +
+
+
+ <% stats = [ + { icon: "fa-bolt", value: "< 200ms", label: "API latency" }, + { icon: "fa-infinity", value: "Unlimited", label: "Gateways" }, + { icon: "fa-shield-halved", value: "SHA-256", label: "Key hashing" }, + { icon: "fa-rotate", value: "3× retry", label: "Auto backoff" } + ] %> + <% stats.each do |s| %> +
+ + <%= s[:value] %> + <%= s[:label] %> +
+ <% end %> +
+
+
+ + +
+
+
+

Capabilities

+

Everything you need to route SMS at scale

+

From a single device to a distributed fleet — message queuing, delivery tracking, and OTP workflows are built in.

+
+ +
+ <% features = [ + { icon: "fa-paper-plane", color: "from-sky-400 to-sky-600", title: "Send SMS", desc: "Queue outbound messages and let the gateway fleet deliver them. Automatic retries with exponential backoff." }, + { icon: "fa-arrow-down", color: "from-emerald-400 to-emerald-600", title: "Receive SMS", desc: "Inbound messages land in your inbox instantly and fire webhooks so your app reacts in real time." }, + { icon: "fa-shield-halved", color: "from-violet-400 to-violet-600", title: "OTP Generation", desc: "Six-digit codes with 5-minute expiry, 3-attempt limits, and 3-per-hour rate caps per phone number." }, + { icon: "fa-plug", color: "from-amber-400 to-orange-600", title: "Webhooks", desc: "HMAC-signed event payloads for sms_received, sms_sent and sms_failed with configurable retries." }, + { icon: "fa-tower-broadcast", color: "from-pink-400 to-rose-600", title: "Realtime WebSocket", desc: "Bidirectional Action Cable channel keeps every gateway in sync — heartbeats, delivery reports, and commands." }, + { icon: "fa-mobile-screen", color: "from-blue-400 to-indigo-600", title: "Gateway Fleet", desc: "Register unlimited Android devices. Priority-based routing, health checks, and live status monitoring." } + ] %> + <% features.each_with_index do |f, i| %> +
+
+ +
+

<%= f[:title] %>

+

<%= f[:desc] %>

+
+ <% end %> +
+
+
+ + +
+
+
+

Architecture

+

How messages flow

+

A clean, asynchronous pipeline keeps the API fast and delivery resilient.

+
+ +
+ <% steps = [ + { n: "01", icon: "fa-code", title: "Client calls API", desc: "POST /api/v1/sms/send with a Bearer API key. Phone numbers are validated with Phonelib." }, + { n: "02", icon: "fa-database", title: "Message queued", desc: "SmsMessage is persisted and SendSmsJob is enqueued via Sidekiq. The API returns instantly with a message_id." }, + { n: "03", icon: "fa-route", title: "Gateway selected", desc: "SendSmsJob picks the highest-priority online, active gateway and broadcasts over Action Cable." }, + { n: "04", icon: "fa-mobile-screen-button", title: "Android delivers", desc: "The gateway device sends the SMS via its native radio and reports back a delivery status." }, + { n: "05", icon: "fa-circle-check", title: "Status & webhooks", desc: "Delivery/failed status is recorded; webhooks fire for subscribed events. Failed messages auto-retry up to 3×." } + ] %> + <% steps.each do |s| %> +
+ <%= s[:n] %> + +

<%= s[:title] %>

+

<%= s[:desc] %>

+
+ <% end %> +
+
+
+ + +
+
+
+

Quick Start

+

Send your first message

+
+ +
+ +
+
+ 1 +

Register a gateway device

+
+

No auth required — receive a gateway API key back.

+
+# Register your Android device
+curl -X POST https://your-host/api/v1/gateway/register \
+  -H "Content-Type: application/json" \
+  -d '{"device_id":"pixel-001","name":"Office Phone"}' +
+
+ +
+
+ + +
+
+ 2 +

Connect via WebSocket

+
+

Keep the device online and ready to receive commands.

+
+# wscat
+wscat -c "ws://your-host:3000/cable?api_key=gw_live_..." +
+
+ + +
+
+ 3 +

Send an SMS

+
+

Use a client API key (api_live_…) in the Authorization header.

+
+curl -X POST https://your-host/api/v1/sms/send \
+  -H "Authorization: Bearer api_live_..." \
+  -H "Content-Type: application/json" \
+  -d '{"to":"+14155551234","message":"Hello from MySMSAPio!"}' +
+
+ +
+
+# → 202 Accepted
+{
+  "success": true,
+  "message_id": "msg_3f8a9c1b2e7d4f60",
+  "status": "queued"
+} +
+
+
+ +
+ <%= link_to docs_path, class: "btn-ghost inline-flex items-center gap-2 rounded-xl px-6 py-3 text-sm font-semibold text-slate-200" do %> + Read the full API reference + <% end %> +
+
+
+ + +
+
+
+

Built on a modern stack

+

Production-ready by default

+
+
+ <% stack = ["Rails 8.0", "Ruby 3.4", "PostgreSQL", "Redis 7", "Sidekiq 7", "Action Cable", "Puma + Thruster", "Docker / Kamal", "Tailwind CSS", "Phonelib", "HTTParty", "ROTP"] %> + <% stack.each do |tech| %> + <%= tech %> + <% end %> +
+
+
+ + +
+
+
+
+

Ready to send your first SMS?

+

Dive into the complete API reference or jump straight into the admin console to manage your gateways.

+
+ <%= link_to docs_path, class: "btn-accent inline-flex items-center gap-2 rounded-xl px-7 py-3.5 text-base font-semibold text-slate-950" do %> + API Documentation + <% end %> + <%= link_to admin_login_path, class: "btn-ghost inline-flex items-center gap-2 rounded-xl px-7 py-3.5 text-base font-semibold text-slate-200" do %> + Open Admin Console + <% end %> +
+
+
+