# Regantis SMS Platform — Canonical End-to-End Implementation Plan

**Document type:** Canonical implementation specification / development handoff  
**Project:** Regantis SMS Platform  
**Primary hostname:** `sms.regantis.technology`  
**Version:** 1.1  
**Date:** 2026-08-27  
**Status:** Architecture approved for implementation  
**Purpose:** This is the default context file to give every new development chat working on the SMS module. A new chat should read this file first and continue from it instead of redesigning the architecture.

---

# 1. Project objective

Build a standalone, self-hosted SMS SaaS/control platform at:

```text
https://sms.regantis.technology
```

The platform sends SMS through **physical Android phones containing real SIM cards** from mobile operators such as Vodafone, Orange, Digi, or any future operator.

We are **not** using SMS aggregators such as Twilio, Infobip, SMSLink, SendSMS, SMPP wholesalers, or similar services.

The actual delivery chain is:

```text
API client
    ↓ HTTPS
sms.regantis.technology
    ↓ queue/routing
Android gateway device
    ↓ Android telephony API
selected physical SIM
    ↓
mobile operator network
    ↓
recipient phone
```

The platform must support:

- N clients/tenants.
- N mobile operators.
- N commercial operator accounts/contracts.
- N physical SIM cards.
- N Android gateway devices.
- Single-SIM and dual-SIM devices.
- Different SIMs for different clients.
- Different operator contracts/invoices.
- Several SIMs for one client.
- Primary/fallback routes.
- Per-SIM and per-client rate limits.
- Per-SIM and per-client quotas.
- Usage accounting.
- Operator invoice tracking.
- Future client billing.
- Full API access.
- A standalone administration frontend.
- Later integration with `crm.regantis.technology`.
- At that later stage, the CRM will own the business-level mapping of each client/domain to its dedicated SIM and dedicated Android phone/gateway, together with the client's main SMS configuration.
- For the initial implementation and test phases, the CRM is intentionally not involved.

The SMS server must remain fully usable for standalone testing before CRM integration.

---

# 2. Decisions that are already final

New development chats must preserve these decisions unless there is a concrete technical blocker.

## 2.1 Hosting

Use the existing Regantis cPanel/CloudLinux server.

Current platform is suitable:

```text
CloudLinux 9.x
KVM
cPanel/WHM
Apache
```

Do not create another VPS for v1.

Create:

```text
sms.regantis.technology
```

All SMS API, gateway communication, administration, queues and technical execution data belong to this subdomain/service.

For the initial implementation and testing:

```text
1 mobile operator/provider
1 provider account/contract
1 physical SIM
1 Android phone
1 gateway
1 test client/route
```

The CRM is intentionally not involved until the SMS server and Android gateway are proven end-to-end.

Near the end of the project, `crm.regantis.technology` will become the business configuration layer for real clients/domains. Each CRM client/domain will normally have its own dedicated SIM and dedicated Android phone/gateway, and the CRM will hold the main client-level SMS configuration and mapping.

## 2.2 SMS transport

Transport is physical SIM-based.

Do not add:

- Twilio
- Infobip
- SMSLink
- SendSMS
- Vonage
- Kannel
- Jasmin
- SMPP
- external SMS termination providers

unless a future requirement explicitly changes the architecture.

## 2.3 Gateway hardware

Initial gateway hardware:

```text
Android phone
+ physical SIM
+ Regantis SMS Gateway app
```

The device can connect through Wi-Fi or mobile data.

The server never requires inbound connectivity to the phone. The phone initiates all connections to the server.

## 2.3.1 Initial test topology

The first real deployment must be deliberately simple:

```text
sms.regantis.technology
        ↓
one configured route
        ↓
one Android phone
        ↓
one physical SIM
        ↓
one mobile operator network
        ↓
test recipient
```

Do not implement CRM synchronization, multiple clients, multiple phones, billing integration, or complex routing before this path works reliably.

The initial test entities can live directly in the SMS server database/admin panel.

## 2.3.2 Final CRM ownership model

The intended production business model is:

```text
crm.regantis.technology
        │
        ├── Client/domain A
        │      ├── SMS configuration
        │      ├── dedicated SIM A
        │      └── dedicated Android gateway A
        │
        ├── Client/domain B
        │      ├── SMS configuration
        │      ├── dedicated SIM B
        │      └── dedicated Android gateway B
        │
        └── Client/domain N
               ├── SMS configuration
               ├── dedicated SIM N
               └── dedicated Android gateway N
                    │
                    ▼
             sms.regantis.technology
                    │
             technical execution
```

Default production assumption:

```text
1 CRM client/domain
    ↓
1 dedicated SIM
    ↓
1 dedicated Android phone/gateway
```

The SMS server remains responsible for technical execution, queueing, device communication, delivery state, reconciliation, and durable transport history.

The CRM integration is intentionally deferred until the standalone SMS path is stable.

## 2.4 Backend stack

Recommended v1 backend:

```text
Node.js 22
Express
ws
MariaDB
Redis
BullMQ or an equivalent Redis-backed internal queue
mysql2/promise
```

Use plain SQL migrations and `mysql2`; do not introduce an ORM unless implementation demonstrates a concrete benefit.

## 2.5 Android stack

Use a **native Kotlin Android application**, not React Native, for the gateway.

Reason: SMS sending, SIM subscription management, foreground services, boot handling, telephony callbacks and background reliability are the core of this application. Native Kotlin removes an unnecessary bridge and gives direct access to Android APIs.

Recommended:

```text
Kotlin
AndroidX
Room / SQLite
OkHttp WebSocket
WorkManager for periodic maintenance only
native ForegroundService
```

## 2.6 Admin frontend

Build a standalone admin frontend at:

```text
https://sms.regantis.technology/admin
```

Recommended stack:

```text
React
Vite
Bootstrap 5
```

It consumes the SMS server's own admin API.

Initially this admin is used to configure the single test provider/account/SIM/gateway/route.

Later, real client/domain configuration will move to or be managed from `crm.regantis.technology`. The standalone SMS admin remains useful for technical operations, diagnostics, gateways, queue state, transport history and system-level support.

## 2.7 Database authority

MariaDB is the source of truth.

Redis is not the authoritative record of whether a message exists or was sent.

Rules:

```text
MariaDB = durable state and audit history
Redis   = queue, locks, rate limiting, transient presence
```

A Redis flush must not lose message history or cause already-sent messages to be sent again.

---

# 3. High-level architecture

```text
                           INTERNET
                               │
                               │ HTTPS / WSS :443
                               ▼
                ┌───────────────────────────────┐
                │ sms.regantis.technology      │
                │                               │
                │ Apache / TLS                  │
                │       │                       │
                │       ▼                       │
                │ Node.js SMS Server            │
                │                               │
                │ ┌───────────────────────────┐ │
                │ │ REST API                  │ │
                │ │ Admin API                 │ │
                │ │ Gateway API               │ │
                │ │ WebSocket gateway         │ │
                │ │ Authentication            │ │
                │ │ Routing                   │ │
                │ │ Queue                     │ │
                │ │ Rate limits               │ │
                │ │ Reconciliation            │ │
                │ │ Usage / billing           │ │
                │ └─────────────┬─────────────┘ │
                │               │               │
                │       ┌───────┴───────┐       │
                │       │               │       │
                │       ▼               ▼       │
                │    MariaDB          Redis     │
                └───────────────┬───────────────┘
                                │
                        WSS / HTTPS outbound
                                │
             ┌──────────────────┼────────────────────┐
             ▼                  ▼                    ▼
       Android GW #1      Android GW #2        Android GW #N
             │                  │                    │
       SIM Vodafone        SIM Orange            SIM Digi
             │                  │                    │
             └──────────────────┼────────────────────┘
                                ▼
                         mobile networks
                                │
                                ▼
                           recipients
```

---

# 3.1 Initial implementation boundary

For the first implementation, keep the system intentionally narrow:

```text
NO CRM
NO multi-client SaaS workflow
NO invoice integration
NO N-provider routing
```

Only prove:

```text
sms.regantis.technology
        ↓
one configured test client/route
        ↓
one Android phone
        ↓
one SIM
        ↓
one mobile operator
        ↓
SMS received
```

Once sending, sent callbacks, delivery callbacks, reconnect/restart safety and duplicate prevention are proven, expand the server to the multi-client structures already defined in this document.

CRM integration is one of the last phases.

# 4. Domain and public endpoints

Everything initially lives on the SMS hostname.

```text
https://sms.regantis.technology/
https://sms.regantis.technology/admin
https://sms.regantis.technology/api/v1/
https://sms.regantis.technology/gateway/v1/
wss://sms.regantis.technology/gateway/v1/ws
```

Recommended health endpoints:

```text
GET /health/live
GET /health/ready
```

`/health/live` only proves the Node process is alive.

`/health/ready` verifies required dependencies:

- MariaDB reachable.
- Redis reachable.
- migrations compatible.
- server ready to accept work.

Do not expose MariaDB or Redis publicly.

---

# 5. Server deployment design

## 5.1 Existing cPanel account

Use a dedicated application directory, outside the public document root when possible.

Example:

```text
/home/CPANEL_USER/sms_server/
```

Suggested structure:

```text
sms_server/
├── app.js
├── package.json
├── package-lock.json
├── config/
│   ├── default.js
│   └── local.js
├── src/
│   ├── api/
│   ├── admin/
│   ├── auth/
│   ├── database/
│   ├── gateway/
│   ├── messages/
│   ├── queue/
│   ├── routing/
│   ├── usage/
│   ├── billing/
│   ├── webhooks/
│   ├── security/
│   ├── services/
│   └── util/
├── migrations/
├── scripts/
├── public/
│   └── admin/
├── logs/
└── tests/
```

The Android source should be maintained separately from the deployed Node application, for example in the project repository:

```text
regantis-sms/
├── server/
├── admin/
├── android/
├── database/
└── docs/
```

## 5.2 Node process strategy

Production preference:

- persistent Node.js process;
- run as the cPanel user;
- managed by `systemd`;
- listen only on localhost;
- Apache/cPanel terminates TLS and reverse-proxies to Node.

Example internal listener:

```text
127.0.0.1:3100
```

Public traffic:

```text
https://sms.regantis.technology
        ↓
Apache
        ↓
127.0.0.1:3100
```

The same reverse proxy must support WebSocket upgrades for:

```text
/gateway/v1/ws
```

CloudLinux/cPanel Passenger is also technically available and Node 22 is supported by current cPanel Application Manager installations. However, because this service maintains long-lived WebSocket connections to physical gateway devices, the canonical production setup should prefer a dedicated persistent Node service unless Passenger WebSocket behavior has been explicitly load/restart tested on this server.

Do not change this merely to reduce setup commands.

## 5.3 TLS

Use cPanel AutoSSL for:

```text
sms.regantis.technology
```

All HTTP requests redirect to HTTPS.

All device WebSockets use:

```text
wss://
```

Never allow a production gateway to use plain `ws://`.

## 5.4 Configuration and secrets

Keep production secrets outside the public webroot and out of Git.

Example:

```text
/home/CPANEL_USER/sms_server/config/local.js
```

File permissions:

```text
0600
```

Store:

- DB credentials.
- Redis credentials/ACL user.
- server signing/encryption keys.
- admin session secret.
- webhook HMAC master key.
- encryption key identifiers.

Do not put secrets in frontend code.

Do not log secrets.

---

# 6. MariaDB database

Create a dedicated database and user.

Logical database name:

```text
regantis_sms
```

Actual cPanel-prefixed name may differ.

Use:

```text
utf8mb4
utf8mb4_unicode_ci
```

All timestamps should be stored as UTC.

The frontend converts to the desired display timezone.

---

# 7. Core data model

The following entities are deliberately separate.

```text
Mobile operator/provider
        ↓
Commercial provider account / contract
        ↓
Physical SIM
        ↓
Gateway slot/device assignment
        ↓
Client route
```

Do not collapse these into one "provider" table.

## 7.1 Mobile operator

Examples:

```text
Vodafone
Orange
Digi
Other
```

Table:

```text
sms_provider
```

Suggested fields:

```text
provider_id
code
name
country_code
active
notes
created_at
updated_at
```

`provider` means mobile network/operator in this project, not an SMS aggregator.

## 7.2 Provider account / contract

A provider can have multiple commercial accounts/contracts.

Example:

```text
Vodafone
├── Regantis corporate contract
├── Client A direct contract
└── Client B direct contract
```

Table:

```text
sms_provider_account
```

Suggested fields:

```text
provider_account_id
provider_id
name
contract_reference
account_owner_type
account_owner_client_id nullable
billing_name
billing_currency
billing_cycle_day
monthly_fixed_cost nullable
included_segments nullable
overage_cost_per_segment nullable
active
notes
created_at
updated_at
```

This entity is important because SIMs may appear on different invoices even when they use the same mobile operator.

## 7.3 Client / tenant

The SMS server is multi-tenant from day one.

Table:

```text
sms_client
```

Suggested fields:

```text
client_id
client_code
name
status
default_country_code
timezone
monthly_segment_limit nullable
daily_segment_limit nullable
max_segments_per_message
created_at
updated_at
```

Possible status values:

```text
active
suspended
disabled
```

The future CRM will map CRM clients to these SMS clients.

Do not make `sms_client_id` depend on a CRM customer ID.

Later add a mapping/reference field if required.

## 7.4 Physical SIM

Table:

```text
sms_sim
```

Suggested fields:

```text
sim_id
provider_account_id
label
msisdn
country_code
iccid_masked nullable
status
activation_date nullable
deactivation_date nullable
billing_fixed_cost nullable
included_segments nullable
overage_cost_per_segment nullable
daily_limit nullable
monthly_limit nullable
minute_rate_limit nullable
hour_rate_limit nullable
notes
created_at
updated_at
```

Suggested statuses:

```text
active
paused
quota_exhausted
blocked
inactive
retired
```

Do not use Android `subscriptionId` as the permanent identity of a SIM. Android subscription IDs are runtime identifiers and can change.

The phone number/MSISDN should be entered and confirmed administratively.

ICCID can be stored only if accessible and useful. Do not make the system dependent on reading ICCID from Android because modern Android access may vary by device/permissions.

## 7.5 Gateway device

A gateway is a physical Android device installation.

Table:

```text
sms_gateway
```

Suggested fields:

```text
gateway_id
gateway_uuid
name
status
enabled
manufacturer
model
android_version
sdk_version
app_version
device_fingerprint_hash nullable
credential_hash
provisioned_at
last_connected_at
last_seen_at
last_ip
battery_percent nullable
is_charging nullable
network_type nullable
connection_version
created_at
updated_at
```

Statuses:

```text
provisioning
online
offline
degraded
disabled
revoked
```

Never store the raw gateway credential after provisioning.

Store only a secure hash.

## 7.6 Gateway SIM slots

A gateway and a SIM are different objects.

One phone can have:

```text
slot 0 → SIM A
slot 1 → SIM B
```

Table:

```text
sms_gateway_slot
```

Suggested fields:

```text
gateway_slot_id
gateway_id
slot_index
sim_id nullable
runtime_subscription_id nullable
carrier_display_name nullable
active
last_verified_at
last_changed_at
created_at
updated_at
```

A SIM can be moved from one phone to another without losing billing/history.

`runtime_subscription_id` is transient and can be refreshed after boot or SIM changes.

## 7.7 Client routing

A client can have several SIM routes.

Table:

```text
sms_client_route
```

Suggested fields:

```text
route_id
client_id
sim_id
priority
weight
enabled
allow_fallback
country_prefix nullable
minute_limit_override nullable
hour_limit_override nullable
daily_limit_override nullable
monthly_limit_override nullable
created_at
updated_at
```

Examples:

```text
Client A
priority 10 → Vodafone SIM 1
priority 20 → Vodafone SIM 2
priority 30 → Orange SIM 1
```

The route refers to a SIM, not directly to a phone. The server resolves the currently assigned gateway slot.

---

# 8. Authentication model

There are three distinct authentication contexts.

## 8.1 Admin users

Browser administrators.

Tables:

```text
sms_admin_user
sms_admin_session
```

Minimum:

```text
admin_user_id
email
name
password_hash
role
active
last_login_at
created_at
updated_at
```

Password hashing:

```text
Argon2id
```

Recommended roles:

```text
superadmin
operator
billing
viewer
```

Start with `superadmin`; keep role support in schema.

Admin web authentication should use secure HTTP-only cookies, not an API token stored in browser localStorage.

Apply CSRF protection to state-changing admin requests.

## 8.2 API clients

Table:

```text
sms_api_key
```

Fields:

```text
api_key_id
client_id nullable
name
key_prefix
key_hash
scopes
active
last_used_at
expires_at nullable
created_at
revoked_at nullable
```

Generate at least 256 bits of random entropy.

Example visible token:

```text
sms_live_a1b2c3.......
```

Only show the complete secret once.

Store:

```text
SHA-256(secret)
```

Because keys are randomly generated high-entropy secrets, a fast cryptographic hash is acceptable for lookup.

Normal client keys are scoped to one `sms_client`.

A client-scoped API key must not accept an arbitrary `client_id` from the caller.

The server derives the client from the API key.

A future trusted CRM/service account may have an explicit multi-client scope.

## 8.3 Gateway credentials

Each physical device gets its own credential.

Example:

```text
gw_live_....
```

Never share one gateway password across all phones.

A compromised device must be revocable independently.

Store credential hash only.

Android stores the credential using Android Keystore-backed encrypted storage.

---

# 9. Gateway provisioning

Provisioning should be simple enough that adding a new phone takes minutes.

## 9.1 Server workflow

Admin:

```text
Admin
→ Gateways
→ Add gateway
```

Server creates:

```text
gateway_id
one-time provisioning token
expiration
```

Token expires after approximately:

```text
10 minutes
```

and is valid once.

Table:

```text
sms_gateway_provision_token
```

Fields:

```text
token_id
gateway_id
token_hash
expires_at
claimed_at nullable
created_by
created_at
```

## 9.2 QR code

Admin page shows QR containing only the provisioning URL/token.

Example concept:

```text
https://sms.regantis.technology/gateway/provision?t=<one-time-token>
```

Do not encode permanent credentials in the QR.

## 9.3 Android workflow

Android app:

```text
Install
→ initial setup
→ become eligible/default SMS handler
→ permissions
→ scan QR
→ claim provisioning token
→ receive permanent gateway credential
→ store credential in secure local storage
→ connect WSS
```

Provision request sends device metadata:

```json
{
  "app_version": "1.0.0",
  "manufacturer": "Samsung",
  "model": "SM-A...",
  "android_version": "16",
  "sdk_version": 36,
  "sim_slots": [
    {
      "slot_index": 0,
      "subscription_id": 4,
      "carrier_name": "Vodafone RO"
    }
  ]
}
```

Do not trust device-reported provider data as billing configuration.

The admin still assigns the physical SIM record to the slot.

---

# 10. Android platform constraints that must be respected

This section is important. Do not implement the Android gateway as a generic background JavaScript/WebSocket app.

## 10.1 SMS permission

Modern Android classifies `SEND_SMS` as a dangerous, hard-restricted permission.

The gateway must be designed as a dedicated SMS application/device deployment.

Canonical approach:

1. Make Regantis SMS Gateway eligible for the Android SMS role.
2. During device provisioning, set it as the dedicated device's default SMS app where required/appropriate.
3. Request only necessary runtime permissions.
4. If a manufacturer/Android build still blocks restricted permissions for sideloaded packages, install/allowlist through the managed installer/ADB/MDM used for the dedicated fleet.

These are dedicated Regantis gateway devices, not a consumer Play Store application.

Do not assume that declaring:

```xml
<uses-permission android:name="android.permission.SEND_SMS" />
```

alone guarantees the permission on every modern Android device.

## 10.2 Foreground execution

The gateway must remain available with the screen off.

Use a foreground service with the Android foreground service type appropriate for remote messaging:

```text
remoteMessaging
```

Manifest permissions/types must match the current target SDK.

The foreground service must show a persistent status notification such as:

```text
Regantis SMS Gateway
Connected
Gateway GW-000123
```

Do not use `dataSync` as the permanent service type; modern Android places time limits on long-running `dataSync` foreground services.

## 10.3 No Firebase requirement

The architecture does not require FCM.

Gateway communication is direct:

```text
Android
   ↓ WSS
sms.regantis.technology
```

For dedicated devices:

- configure battery mode as unrestricted;
- ensure the app is not placed into vendor "sleeping apps";
- enable autostart where the OEM provides such a setting;
- test screen-off operation;
- test after reboot;
- keep gateway devices powered/charging in normal operation.

## 10.4 Boot

Register for boot completion and restore gateway operation.

The app should reconnect automatically after device restart.

If Android force-stop semantics prevent automatic restart after a manual force-stop, the UI must clearly show that the gateway needs to be opened/re-enabled.

## 10.5 Multi-SIM

Never use the default `SmsManager` on a multi-SIM device when the route requires a specific SIM.

Obtain active subscriptions through `SubscriptionManager`.

For current Android APIs use the equivalent of:

```kotlin
val base = context.getSystemService(SmsManager::class.java)
val smsManager = base.createForSubscriptionId(subscriptionId)
```

Then call `sendTextMessage` / `sendMultipartTextMessage`.

The runtime subscription must match the server-assigned gateway slot.

If the SIM mapping changes unexpectedly:

```text
STOP SENDING
→ mark gateway/SIM mapping degraded
→ report sim.changed
→ require reconciliation/admin confirmation
```

Do not silently send from another slot.

---

# 11. Android app architecture

Suggested package modules:

```text
app/
├── auth/
├── provisioning/
├── gateway/
├── websocket/
├── telephony/
├── database/
├── status/
├── boot/
├── settings/
└── ui/
```

Key components:

```text
MainActivity
GatewayForegroundService
GatewayWebSocketClient
ProvisioningManager
GatewayCredentialStore
SimManager
SmsDispatcher
SmsSentReceiver
SmsDeliveredReceiver
BootReceiver
LocalAttemptRepository
ReconciliationManager
```

## 11.1 Local persistence

Use Room/SQLite.

The phone must maintain a durable local ledger of message attempts.

Suggested local table:

```text
gateway_attempt
```

Fields:

```text
attempt_id
message_id
server_command_id
subscription_id
destination
body_hash
parts_count
state
received_at
accepted_at
send_started_at
sent_at
delivered_at
failed_at
last_error_code
reported_to_server
```

Do not rely only on in-memory state.

This local ledger is essential for reconnect/restart reconciliation.

## 11.2 Secure credential storage

Store gateway credential using Android Keystore-backed encrypted storage.

Never display the complete credential after provisioning.

Allow "Revoke / reset gateway" from server admin.

---

# 12. SMS sending implementation

## 12.1 Number format

Internal canonical destination format:

```text
E.164
```

Example:

```text
+40722123456
```

API validation should normalize when possible using the client's configured default country, but the canonical stored value is E.164.

Prefer strict API behavior rather than guessing ambiguous numbers.

## 12.2 SMS segmentation

Do not assume:

```text
1 API message = 1 billed SMS
```

A long SMS may use several network segments.

Use Android's current `SmsManager.divideMessage()` for the final device-specific split.

Concept:

```kotlin
val parts = smsManager.divideMessage(body)

if (parts.size == 1) {
    smsManager.sendTextMessage(...)
} else {
    smsManager.sendMultipartTextMessage(...)
}
```

The Android app reports the actual number of parts to the server.

Usage and billing use actual segments.

General expectations:

- GSM 7-bit typically permits more characters per segment.
- Unicode/UCS-2 typically permits fewer.
- emojis or non-GSM characters can substantially increase segment count.
- concatenated multipart SMS has lower per-part payload than a single SMS.

Do not implement billing using JavaScript string length.

## 12.3 Sent callback

Create a unique `PendingIntent` for every SMS part.

The Android sent receiver reports the actual Android result.

Record:

```text
part sent OK
or
part send failed + result code
```

A message is `sent` only when every required part has a successful sent result.

## 12.4 Delivery callback

Use delivery `PendingIntent` for every part where supported.

A message is `delivered` only when required part delivery receipts have arrived.

Important:

A carrier/device may not always return a usable delivery report.

Therefore:

```text
sent != delivered
```

Do not convert `sent` to `delivered` after an arbitrary timeout.

Support:

```text
delivery_pending
delivery_unknown
delivered
```

---

# 13. Message state machine

Recommended message statuses:

```text
queued
routing
assigned
accepted
sending
sent
delivery_pending
partially_delivered
delivered
failed
unknown
cancelled
expired
```

Simplified lifecycle:

```text
queued
  ↓
routing
  ↓
assigned
  ↓
accepted
  ↓
sending
  ↓
sent
  ↓
delivery_pending
  ↓
delivered
```

Failures can occur before or after assignment.

The `unknown` state is intentional and important.

If the server cannot prove whether the phone sent the SMS, it must not blindly resend it.

---

# 14. Exactly-once behavior and duplicate prevention

SMS sending is an irreversible external side effect.

A distributed server/phone system cannot guarantee mathematically perfect exactly-once delivery across every network failure.

The platform must instead implement **at-most-once-biased dispatch with reconciliation**.

This means avoiding duplicate SMS is more important than automatically retrying an uncertain attempt.

## 14.1 Idempotency at API level

Require/support:

```http
Idempotency-Key: <unique-value>
```

For every API `POST /messages`.

Store an idempotency key with a unique database constraint:

```text
client_id + idempotency_key
```

If the caller repeats the request, return the original message record.

Do not create a second SMS.

## 14.2 Server-to-device attempt ID

Every physical dispatch uses:

```text
message_id
attempt_id
command_id
```

The server never sends a bare destination/body command without durable IDs.

## 14.3 Device ACK

Protocol:

```text
server → sms.send
device → sms.accepted
device → local DB persisted
device → invoke SmsManager
```

The device must persist the attempt locally before calling Android telephony.

## 14.4 Connection drop after dispatch

Problem:

```text
server sends sms.send
phone sends SMS
network/WebSocket drops
server never sees result
```

The wrong behavior is:

```text
timeout → send same SMS from another SIM
```

because the recipient may receive the SMS twice.

Correct behavior:

```text
attempt → unknown/reconciling
wait for gateway reconnect
request local attempt state
reconcile
```

Only use fallback routing automatically if the previous attempt is known not to have been sent.

## 14.5 Safe retry classes

Errors can be classified:

```text
DEFINITE_NOT_SENT
RETRYABLE_NOT_SENT
UNCERTAIN
SENT
```

Automatic retry is allowed only for failure classes where the system can confidently conclude the network send did not occur.

Uncertain cases remain `unknown` until reconciliation/manual policy resolves them.

---

# 15. Server database message tables

## 15.1 Messages

Table:

```text
sms_message
```

Suggested fields:

```text
message_id
public_id
client_id
api_key_id nullable
idempotency_key
destination_e164
body_ciphertext
body_iv
body_tag
body_hash
reference nullable
status
priority
segments_estimated nullable
segments_actual nullable
selected_sim_id nullable
selected_gateway_id nullable
current_attempt_id nullable
queued_at
assigned_at nullable
accepted_at nullable
sent_at nullable
delivered_at nullable
failed_at nullable
expires_at nullable
last_error_code nullable
last_error_message nullable
created_at
updated_at
```

Use a non-sequential public identifier such as ULID/UUID.

Do not expose sequential DB IDs in the public API.

## 15.2 Attempts

Table:

```text
sms_message_attempt
```

Fields:

```text
attempt_id
message_id
sim_id
gateway_id
gateway_slot_id
command_id
attempt_number
status
dispatch_at nullable
accepted_at nullable
send_started_at nullable
sent_at nullable
failed_at nullable
unknown_at nullable
failure_class nullable
error_code nullable
error_message nullable
segments_count nullable
created_at
updated_at
```

## 15.3 Parts

Table:

```text
sms_message_part
```

Fields:

```text
part_id
attempt_id
part_index
part_count
status
android_result_code nullable
android_error_code nullable
sent_at nullable
delivered_at nullable
delivery_pdu_hash nullable
created_at
updated_at
```

Do not store raw delivery PDU indefinitely unless required for debugging.

---

# 16. WebSocket gateway protocol

Protocol must be versioned.

Endpoint:

```text
wss://sms.regantis.technology/gateway/v1/ws
```

Gateway connects with its permanent credential.

Use an Authorization header from the Android client where possible:

```http
Authorization: Bearer gw_live_...
```

Do not put permanent gateway credentials into a URL query string.

## 16.1 Device hello

Device → server:

```json
{
  "type": "gateway.hello",
  "protocol": 1,
  "gateway_uuid": "...",
  "app_version": "1.0.0",
  "android_version": "16",
  "battery_percent": 92,
  "charging": true,
  "slots": [
    {
      "slot_index": 0,
      "subscription_id": 4,
      "carrier_name": "Vodafone RO"
    }
  ]
}
```

## 16.2 Heartbeat

Device → server every configurable interval, for example 20–30 seconds:

```json
{
  "type": "gateway.heartbeat",
  "timestamp": "...",
  "battery_percent": 91,
  "charging": true,
  "network_type": "wifi",
  "queue_local": 0
}
```

The server should not mark the gateway offline after one missed heartbeat.

Use configurable thresholds, for example:

```text
healthy   < 60 sec
degraded  60–120 sec
offline   > 120 sec
```

Final values should be tested.

## 16.3 Dispatch command

Server → device:

```json
{
  "type": "sms.send",
  "protocol": 1,
  "command_id": "...",
  "message_id": "...",
  "attempt_id": "...",
  "slot_index": 0,
  "subscription_id": 4,
  "to": "+40722123456",
  "body": "Test",
  "expires_at": "..."
}
```

The device validates:

- gateway enabled;
- slot exists;
- subscription is still active;
- command not already processed;
- attempt not already in local ledger;
- message not expired.

## 16.4 Accepted

Device → server:

```json
{
  "type": "sms.accepted",
  "command_id": "...",
  "message_id": "...",
  "attempt_id": "...",
  "parts_count": 1
}
```

## 16.5 Part sent

```json
{
  "type": "sms.part.sent",
  "attempt_id": "...",
  "part_index": 0,
  "part_count": 1,
  "result": "ok"
}
```

## 16.6 Part failed

```json
{
  "type": "sms.part.failed",
  "attempt_id": "...",
  "part_index": 0,
  "part_count": 1,
  "result_code": "...",
  "error_code": "...",
  "failure_class": "DEFINITE_NOT_SENT"
}
```

The server should not blindly trust a device-provided failure classification. Maintain a server-side mapping of Android result codes to retry policy.

## 16.7 Delivered

```json
{
  "type": "sms.part.delivered",
  "attempt_id": "...",
  "part_index": 0,
  "part_count": 1,
  "delivered_at": "..."
}
```

## 16.8 Reconciliation

On every reconnect, device sends IDs/statuses of locally unfinished or recently completed attempts.

Server can request:

```json
{
  "type": "gateway.reconcile.request",
  "since": "..."
}
```

Device returns:

```json
{
  "type": "gateway.reconcile.result",
  "attempts": [
    {
      "attempt_id": "...",
      "state": "sent",
      "parts_count": 2,
      "sent_parts": 2,
      "delivered_parts": 1
    }
  ]
}
```

This is mandatory for production reliability.

---

# 17. HTTP API

Base:

```text
https://sms.regantis.technology/api/v1
```

## 17.1 Send SMS

```http
POST /api/v1/messages
Authorization: Bearer sms_live_...
Idempotency-Key: 27b...
Content-Type: application/json
```

Request:

```json
{
  "to": "+40722123456",
  "message": "Mesaj test",
  "reference": "order:1234"
}
```

Do not accept an arbitrary `client_id` from normal client-scoped API credentials.

Response:

```json
{
  "id": "sms_01...",
  "status": "queued",
  "to": "+40722123456",
  "segments_estimated": 1,
  "created_at": "2026-08-27T14:00:00Z"
}
```

HTTP status:

```text
202 Accepted
```

## 17.2 Get message

```http
GET /api/v1/messages/{public_id}
```

Response example:

```json
{
  "id": "sms_01...",
  "status": "delivered",
  "to": "+40722123456",
  "reference": "order:1234",
  "segments": 1,
  "created_at": "...",
  "sent_at": "...",
  "delivered_at": "..."
}
```

Client API should not expose internal:

- gateway credentials;
- other tenants;
- operator contract information;
- private internal logs.

## 17.3 List messages

```http
GET /api/v1/messages
```

Filters:

```text
status
date_from
date_to
reference
destination
page
limit
```

## 17.4 Cancel queued message

Optional but useful:

```http
POST /api/v1/messages/{id}/cancel
```

Cancellation is allowed only before irreversible dispatch.

Once the phone accepted/sending state begins, cancellation must fail.

## 17.5 Batch

Do not make batch sending a blocker for the first end-to-end milestone.

Later:

```http
POST /api/v1/messages/batch
```

Each item needs its own idempotency identity.

---

# 18. API error format

Use one consistent error shape.

Example:

```json
{
  "error": {
    "code": "NO_AVAILABLE_ROUTE",
    "message": "No active SMS route is currently available.",
    "request_id": "req_..."
  }
}
```

Suggested error codes:

```text
AUTH_REQUIRED
AUTH_INVALID
FORBIDDEN
VALIDATION_FAILED
INVALID_DESTINATION
MESSAGE_TOO_LONG
CLIENT_SUSPENDED
CLIENT_QUOTA_EXCEEDED
SIM_QUOTA_EXCEEDED
RATE_LIMITED
NO_AVAILABLE_ROUTE
MESSAGE_NOT_FOUND
MESSAGE_ALREADY_DISPATCHED
IDEMPOTENCY_CONFLICT
INTERNAL_ERROR
```

Do not return stack traces in production.

---

# 19. Routing engine

Routing occurs on the server.

The API caller normally does not select a physical SIM.

## 19.1 Route eligibility

A route is eligible only if:

```text
client active
route enabled
SIM active
provider account active
gateway enabled
gateway online/healthy
gateway slot assigned to expected SIM
runtime subscription valid
country allowed
client quota available
SIM quota available
client rate available
SIM rate available
message not expired
```

## 19.2 Priority

Start with deterministic priority routing.

Example:

```text
priority 10 → SIM 11
priority 20 → SIM 12
priority 30 → SIM 22
```

Choose the lowest priority number among eligible routes.

If several routes share a priority, use configured weight/round-robin.

## 19.3 Fallback

Fallback occurs only when:

- no dispatch occurred on previous route; or
- previous attempt produced a safe, definite not-sent result.

Do not automatically fallback from an `unknown` attempt.

## 19.4 Manual route

Admin/API privileged users may eventually request a specific SIM for diagnostics, but normal tenant APIs should use configured routing.

---

# 20. Queue design

Queue purposes:

- asynchronous API response;
- dispatch ordering;
- rate control;
- retries;
- offline waiting;
- scheduled reconciliation.

Recommended:

```text
Redis + BullMQ
```

But MariaDB remains authoritative.

Flow:

```text
POST /messages
     ↓
DB transaction inserts sms_message
     ↓
commit
     ↓
enqueue message ID
     ↓
return 202
```

If Redis enqueue fails after DB commit:

```text
message remains queued in DB
```

A recovery scheduler scans for queued DB records missing from execution and re-enqueues them.

This prevents Redis loss from losing SMS jobs.

## 20.1 Queue concurrency

Physical SIM throughput is low.

Do not optimize the platform for hundreds of SMS/second per SIM.

Use per-SIM serialized/limited execution.

A single SIM should not have several simultaneous `SmsManager` jobs unless later device testing proves it safe and operator policy allows it.

---

# 21. Rate limiting and quotas

There are multiple independent limits.

## 21.1 API rate limit

Protect API abuse:

```text
requests/minute per API key
```

## 21.2 Client SMS rate

Example:

```text
20 segments/minute
500/hour
3000/day
```

## 21.3 SIM SMS rate

Every SIM has independent limits.

Example fields:

```text
max_segments_per_minute
max_segments_per_hour
max_segments_per_day
max_segments_per_billing_cycle
```

## 21.4 Billing-cycle quota

Support custom billing cycles, not only calendar month.

Example:

```text
15 Aug → 14 Sep
```

Provider account can define the billing day.

SIM can override limits.

## 21.5 Quota behavior

At configurable thresholds:

```text
80% → warning
90% → warning
100% → stop route or allow paid overage, according to configuration
```

Do not assume every operator plan has the same rules.

---

# 22. Usage accounting

Count actual sent SMS network segments.

Table:

```text
sms_usage_event
```

Suggested fields:

```text
usage_event_id
message_id
attempt_id
client_id
sim_id
provider_account_id
event_type
segments
unit_cost nullable
unit_price nullable
currency
occurred_at
created_at
```

Usage should be append-oriented.

Daily/monthly summary tables can be generated for fast dashboards.

Example:

```text
sms_usage_daily
sms_usage_billing_cycle
```

Do not calculate historical invoices solely from mutable current SIM rates.

Persist the applied cost/rate snapshot with usage or invoice lines.

---

# 23. Operator invoice model

We need to support different invoices/contracts.

Tables:

```text
sms_provider_invoice
sms_provider_invoice_line
```

Invoice fields:

```text
provider_invoice_id
provider_account_id
invoice_number
period_start
period_end
invoice_date
currency
subtotal
tax
total
status
notes
created_at
updated_at
```

Lines can be associated with:

```text
SIM
fixed subscription charge
included usage
overage usage
other fee
```

The SMS server does not need to generate accounting invoices in v1.

It needs to **track the relationship between operator invoice, account, SIM and measured SMS usage**.

Future CRM invoicing can consume this data.

---

# 24. Client billing model

Keep operator cost and client billing separate.

Example:

```text
Operator account:
SIM monthly cost = 12 EUR
included = 5000 segments
overage = 0.03 EUR/segment

Client pricing:
monthly SMS service = 20 EUR
included = 3000
overage = 0.05 EUR/segment
```

Tables:

```text
sms_client_rate_plan
sms_client_rate
```

Do not hard-code one pricing formula.

Support:

- fixed monthly fee;
- included segment quantity;
- price per segment;
- operator pass-through;
- custom client price;
- currency.

The future CRM can use finalized usage to create invoice lines.

---

# 25. Webhooks

Webhooks are useful even before CRM integration.

A client can configure:

```text
message.sent
message.delivered
message.failed
message.unknown
```

Tables:

```text
sms_webhook
sms_webhook_delivery
```

Do not accept an arbitrary callback URL on every send request in v1. Store approved webhook destinations in client configuration to reduce SSRF/security problems.

Sign payloads:

```text
HMAC-SHA256
```

Include:

```text
event_id
timestamp
signature
```

Retry delivery with backoff.

Webhook delivery failure must never change the SMS delivery status.

---

# 26. Admin frontend

Base:

```text
https://sms.regantis.technology/admin
```

## 26.1 Dashboard

Show:

```text
Messages today
Segments today
Sent
Delivered
Failed
Unknown
Queued
Online gateways
Offline gateways
Active SIMs
SIM quota warnings
Client quota warnings
```

Show recent incidents:

```text
gateway offline
SIM changed
quota exceeded
high failure rate
```

## 26.2 Messages

Columns:

```text
ID
client
destination
status
segments
SIM
gateway
created
sent
delivered
reference
```

Filters:

```text
client
status
provider
SIM
gateway
date
destination
reference
```

Message detail shows the state timeline and attempts.

Do not expose full message content in list rows by default.

## 26.3 Clients

CRUD:

```text
client identity
status
default country
timezone
quotas
rate plan
API keys
webhooks
routes
```

## 26.4 Providers

CRUD mobile operators.

## 26.5 Provider accounts

Manage:

```text
operator
contract reference
owner
billing cycle
monthly cost
included segments
overage
currency
```

## 26.6 SIMs

Manage:

```text
operator account
number
label
status
limits
current gateway/slot
current client routes
usage
billing
```

## 26.7 Gateways

Show:

```text
online/offline
device
Android version
app version
battery
charging
last heartbeat
network
SIM slots
last errors
```

Actions:

```text
Add / provision
Disable
Revoke credential
Regenerate provisioning
Request reconnect
View logs
```

## 26.8 Routing

Per client:

```text
SIM
priority
weight
fallback
limits
enabled
```

Include a route simulator:

```text
"Which SIM would be selected right now?"
```

This will be valuable for debugging.

## 26.9 Usage

Views:

```text
by client
by provider
by provider account
by SIM
by day
by billing cycle
```

## 26.10 Invoices

Track operator invoice metadata and reconciliation.

Client invoice generation remains future CRM responsibility unless later changed.

## 26.11 API keys

Create/revoke keys.

Full secret shown once.

## 26.12 System

Show:

```text
API health
DB health
Redis health
server version
gateway protocol version
active WebSocket connections
queue depth
```

---

# 27. Message privacy and GDPR-oriented design

SMS recipient numbers and message bodies can contain personal data.

Minimum rules:

- TLS everywhere.
- no message body in application logs;
- mask destination numbers in normal logs;
- admin access audited;
- API keys never logged;
- gateway tokens never logged;
- configurable message-content retention;
- encrypted backups;
- database access local/private.

Recommended v1 body storage:

```text
AES-256-GCM application-level encryption
```

Persist:

```text
ciphertext
IV
auth tag
hash
```

Keep encryption key outside DB and outside Git.

If message-body encryption is deferred for the very first development milestone, it must be completed before production use.

Suggested retention model:

```text
message metadata: long-term according to accounting/audit need
message body: short configurable period
gateway debug logs: short period
```

A purge job should replace/remove expired body content without deleting required billing metadata.

---

# 28. Logging and audit

Use structured JSON logs.

Never log:

```text
raw API key
raw gateway token
full SMS body
full authorization header
SIM PIN
database password
```

Recommended server logs:

```text
application.log
security.log
gateway.log
worker.log
```

Use log rotation.

Admin actions should also enter:

```text
sms_audit_log
```

Fields:

```text
audit_id
admin_user_id
action
entity_type
entity_id
before_json nullable
after_json nullable
ip
created_at
```

Mask secrets from before/after snapshots.

---

# 29. Gateway observability

Store a limited gateway event history:

```text
connected
disconnected
heartbeat timeout
SIM changed
subscription unavailable
send failure
permission failure
battery low
app outdated
```

Do not write every heartbeat as a permanent database row forever.

Maintain current presence in Redis/DB and persist only meaningful state changes or sampled metrics.

---

# 30. Android UI

The Android application UI can stay minimal.

Primary screen:

```text
Regantis SMS Gateway

Status
● Connected

Gateway
GW-000123

Server
sms.regantis.technology

SIM 1
Vodafone / +40...

SIM 2
Not configured

Today
Messages: 145
Segments: 152
Failed: 2

Queue
0

Battery
92% / Charging

Last server contact
3 seconds ago
```

Actions:

```text
Reconnect
Run diagnostics
View recent local attempts
Provision/reset
Open required Android settings
```

The UI must clearly flag:

```text
SEND_SMS permission missing
not default SMS handler where required
battery optimization active
SIM missing
SIM changed
server unreachable
gateway revoked
```

---

# 31. Android diagnostics

Build a diagnostic screen from the beginning.

Checks:

```text
Internet connectivity
TLS connection
gateway credential
WebSocket
SMS role
SEND_SMS permission
READ_PHONE_STATE permission if used
foreground-service state
active SIM subscriptions
selected subscription
carrier name
test server ping
local database
battery optimization
```

This will save significant development/support time.

---

# 32. Device operations

Recommended physical gateway policy:

- dedicated Android phone;
- not used as a normal employee phone;
- kept powered;
- reliable charger;
- stable Wi-Fi, with cellular data as optional fallback;
- disable battery optimization for gateway app;
- verify after every Android update;
- device labeled with gateway ID and SIM number;
- screen lock allowed;
- avoid rooting production devices;
- auto OS/app updates should be controlled, not random during business hours.

For larger fleets later, consider MDM/device-owner management.

MDM is not required to prove v1.

---

# 33. Gateway app updates

Do not make Google Play mandatory.

Private APK distribution is acceptable.

Build:

```text
RegantisSmsGateway-release.apk
```

Sign with a permanent Regantis signing key.

Back up the signing key securely.

The server records:

```text
app_version
```

Admin dashboard flags:

```text
current
outdated
unsupported
```

Later implement a private update flow if required.

Do not silently download/install APKs without a proper Android device-management strategy.

---

# 34. Security controls

## 34.1 Public API

- TLS only.
- Bearer API key.
- per-key rate limiting.
- scopes.
- request size limits.
- JSON content type.
- strict validation.
- idempotency.
- request IDs.
- no stack traces.

## 34.2 Gateway

- TLS only.
- unique gateway credential.
- credential revocation.
- WebSocket message size limit.
- protocol version validation.
- command IDs.
- replay protection.
- server-side authorization of gateway/SIM assignment.

A phone must not be able to claim:

```text
"I am SIM 17"
```

and send for another tenant without server-side mapping.

## 34.3 Admin

- HTTPS.
- strong password hashing.
- HTTP-only secure session cookies.
- CSRF protection.
- login throttling.
- audit log.
- optional TOTP 2FA before production if the panel is Internet-exposed.

## 34.4 Network

Public:

```text
80
443
```

Private/local only:

```text
MariaDB
Redis
Node internal port
```

---

# 35. Safety limits against platform abuse

Because physical SIMs can be blocked or create unexpected bills, server-side safety is mandatory.

Global kill switches:

```text
disable all sending
disable client
disable provider account
disable SIM
disable gateway
disable route
revoke API key
```

Optional emergency threshold:

```text
global max segments/hour
```

If abnormal traffic is detected, stop queue dispatch rather than continuing blindly.

Admin should be able to pause sending while preserving queued messages.

---

# 36. SMS types and scope

V1 supports:

```text
outbound SMS text
single-part
multipart
Unicode
sent callbacks
delivery callbacks where supported
```

Explicitly out of v1:

```text
MMS
voice calls
SMPP
external SMS aggregators
marketing campaign builder
short codes
USSD
SIM-bank hardware
iOS gateways
```

Inbound SMS can be added later.

The Android role design should avoid blocking future inbound support, but inbound SMS is not required for the first production milestone.

---

# 37. Delivery semantics shown to users

Use precise language.

`sent` means:

```text
Android/telephony accepted the required SMS part(s) as sent successfully.
```

`delivered` means:

```text
delivery report received for the required part(s).
```

`unknown` means:

```text
the system cannot safely prove the final send state.
```

Do not label every successful `SmsManager` call as delivered.

---

# 38. Recovery after server restart

On Node restart:

1. load configuration;
2. connect MariaDB;
3. connect Redis;
4. mark stale in-memory gateway connections offline;
5. accept gateway reconnects;
6. reconcile device local ledgers;
7. scan DB for queued messages;
8. re-enqueue missing queued work;
9. scan `assigned/accepted/sending` messages for reconciliation;
10. never automatically duplicate uncertain sends.

The platform must survive:

```text
Node restart
Redis restart
Apache restart
phone reconnect
phone reboot
Internet interruption
```

without losing durable message records.

---

# 39. Recovery after Redis loss

Redis is disposable execution state.

After Redis flush/restart:

```text
MariaDB scan
→ queued messages restored to execution queue
→ rate counters reconstructed where necessary
→ gateways reconnect/presence rebuilt
```

Messages already marked as `sent` or later must never be re-enqueued.

Messages in ambiguous attempt states enter reconciliation, not fresh dispatch.

---

# 40. Recovery after phone restart

The Android app:

1. starts/restores service;
2. loads permanent gateway credential;
3. loads local attempt ledger;
4. resolves active SIM subscriptions;
5. checks expected SIM/slot;
6. connects to server;
7. sends hello;
8. performs reconciliation;
9. resumes normal dispatch.

If SIM identity/mapping is uncertain, the device remains connected but unavailable for sending.

---

# 41. Failure scenarios that must be handled

## 41.1 Gateway offline before assignment

Message stays queued or another eligible route is selected.

## 41.2 Gateway goes offline after assignment but before acceptance

After timeout and reconciliation logic confirms no device acceptance, dispatch can use another route.

## 41.3 Gateway disconnects after acceptance

Do not resend immediately.

Mark reconciling/unknown and wait for device ledger.

## 41.4 SIM removed

Gateway reports slot change.

Disable route until verified.

## 41.5 Airplane mode/radio unavailable

Return Android failure result.

Retry only according to safe failure classification.

## 41.6 Server cannot reach Redis

API may accept into MariaDB only if recovery design is active, returning queued state, or return temporary unavailable based on final implementation policy.

Do not lose accepted API messages.

## 41.7 MariaDB unavailable

Do not accept a send request.

Return service unavailable.

Never queue a message solely in RAM/Redis without a durable DB record.

## 41.8 Delivery receipt never arrives

Message remains:

```text
sent / delivery_unknown
```

according to aging policy.

Do not mark failed solely because no delivery receipt arrives.

---

# 42. Database transactions and locking

Use transactions for:

- API message creation + idempotency;
- route assignment state transitions;
- attempt creation;
- quota reservation/accounting where required.

Use row-level locking or atomic updates to prevent two workers from assigning the same message.

Example concept:

```text
UPDATE sms_message
SET status='routing'
WHERE message_id=? AND status='queued'
```

Proceed only if exactly one row changed.

Do not rely on JavaScript in-memory locks for correctness.

Redis locks can optimize distribution but database state transitions remain the durable guard.

---

# 43. Message expiry

Support an optional expiration time.

Example:

```text
OTP expires after 5 minutes
notification expires after 1 hour
```

If message is still `queued` after expiry:

```text
status = expired
```

Never dispatch expired messages.

Do not expire a message after device acceptance as a substitute for final-state reconciliation.

---

# 44. Priorities

Suggested priorities:

```text
high
normal
low
```

Examples:

```text
OTP              high
transactional    normal
non-urgent       low
```

Rate limits still apply.

Do not allow one client to starve every other client indefinitely.

---

# 45. API request reference

`reference` is caller-defined correlation data.

Examples:

```text
order:123
invoice:9981
login-otp:abc
```

It is not the idempotency key.

The same reference may appear more than once unless a future client policy says otherwise.

---

# 46. Admin route simulator

Implement after core routing.

Input:

```text
client
destination
message length/type
```

Output:

```text
eligible routes
ineligible routes + reasons
selected route
quota status
rate status
gateway status
estimated segments
```

This dramatically improves supportability.

---

# 47. OpenAPI documentation

Generate/maintain:

```text
OpenAPI 3.x
```

Expose authenticated/internal docs at:

```text
/docs
```

or keep them admin-only.

At minimum document:

```text
POST /api/v1/messages
GET /api/v1/messages/{id}
GET /api/v1/messages
POST /api/v1/messages/{id}/cancel
```

Every future API change must remain versioned.

Do not silently break `/api/v1`.

---

# 48. Versioning

Track:

```text
server version
admin frontend version
gateway APK version
gateway protocol version
database schema version
API version
```

A gateway hello includes:

```text
protocol
app_version
```

Server can reject unsupported protocol versions with a clear upgrade reason.

---

# 49. Repository recommendation

```text
regantis-sms/
├── README.md
├── docs/
│   ├── ARCHITECTURE.md
│   ├── API.md
│   ├── GATEWAY_PROTOCOL.md
│   ├── ANDROID_DEPLOYMENT.md
│   └── OPERATIONS.md
├── server/
│   ├── app.js
│   ├── package.json
│   ├── src/
│   ├── migrations/
│   └── tests/
├── admin/
│   ├── package.json
│   ├── src/
│   └── vite.config.js
└── android/
    ├── app/
    ├── build.gradle.kts
    └── settings.gradle.kts
```

The canonical handoff file may remain at repository root:

```text
REGANTIS_SMS_IMPLEMENTATION_PLAN.md
```

---

# 50. Implementation phases

Do not try to build the entire billing/admin system before the first real SMS works.

The order below is intentional.

---

# Phase 0 — Server preflight

Tasks:

- confirm `sms.regantis.technology` DNS/subdomain;
- issue TLS certificate;
- confirm Node.js 22;
- confirm root access for persistent service/reverse proxy;
- confirm MariaDB;
- install/confirm Redis;
- create application Unix directories;
- create DB/database user;
- verify local Node → MariaDB;
- verify local Node → Redis;
- create initial Git/source structure;
- define production config file.

Acceptance:

```text
GET https://sms.regantis.technology/health/live
→ 200

GET https://sms.regantis.technology/health/ready
→ 200
```

No Android work before the basic server endpoint is stable.

---

# Phase 1 — Backend foundation

Implement:

- Express application;
- global request ID;
- JSON validation;
- error handler;
- MariaDB pool;
- migrations;
- Redis;
- structured logging;
- health endpoints;
- admin authentication skeleton;
- API-key authentication skeleton.

Tables first:

```text
sms_admin_user
sms_client
sms_api_key
sms_provider
sms_provider_account
sms_sim
sms_gateway
sms_gateway_slot
sms_client_route
sms_message
sms_message_attempt
sms_message_part
```

Acceptance:

- migrations run cleanly;
- restart safe;
- health reflects DB/Redis state;
- admin can log in;
- test client/API key can be created.

---

# Phase 2 — Standalone admin UI foundation

Implement pages:

```text
Login
Dashboard
Clients
Providers
Provider Accounts
SIMs
Gateways
Routes
Messages
System
```

At this phase simple CRUD is sufficient.

Acceptance:

- one test client exists;
- one provider account exists;
- one SIM exists;
- one gateway placeholder exists;
- SIM can be assigned to gateway slot;
- SIM can be added as route for client.

No CRM dependency.

---

# Phase 3 — Gateway provisioning protocol

Implement server:

```text
create gateway
create one-time token
claim token
issue gateway credential
authenticate WSS
gateway hello
heartbeat
online/offline presence
revoke credential
```

Implement admin QR flow.

Acceptance:

- a test client can connect to WSS using a provisioned credential;
- invalid/revoked gateway cannot connect;
- admin sees online/offline and last heartbeat.

A temporary command-line test client can be used before Android is ready.

---

# Phase 4 — Android gateway base

Create native Kotlin app.

Implement:

- UI;
- provisioning QR;
- secure credential storage;
- WebSocket;
- foreground `remoteMessaging` service;
- persistent notification;
- boot handling;
- SIM/subscription detection;
- diagnostics;
- Room local database.

Handle modern Android SMS-role/restricted-permission requirements.

Acceptance:

- physical phone provisions successfully;
- remains connected with screen off;
- survives normal network switch;
- reconnects after Wi-Fi interruption;
- returns after reboot;
- admin shows correct device/app/battery/slot state.

---

# Phase 5 — First real SMS

Server:

- create queued message;
- select single configured SIM;
- dispatch `sms.send`;
- record attempt.

Android:

- receive command;
- persist local attempt;
- validate subscription;
- divide message;
- call SmsManager;
- report sent result.

Acceptance:

```text
POST /api/v1/messages
        ↓
queued
        ↓
phone
        ↓
physical SIM
        ↓
recipient receives SMS
```

This is the first major project milestone.

Do not add advanced failover until this path works reliably.

---

# Phase 6 — Multipart and delivery reports

Implement:

- `divideMessage`;
- multipart sent intents;
- multipart delivery intents;
- per-part table;
- final `sent`;
- final `delivered`;
- `delivery_unknown`.

Test:

```text
ASCII short
Romanian diacritics
Unicode
emoji
long multipart
```

Acceptance:

- actual segment count matches device split;
- all part events visible in message detail;
- usage counts segments, not API records.

---

# Phase 7 — Idempotency and reconciliation

Implement:

- API idempotency;
- command IDs;
- device local attempt ledger;
- reconnect reconciliation;
- server restart recovery;
- Redis-loss recovery;
- unknown status;
- safe retry classification.

This phase is mandatory before serious production traffic.

Acceptance tests must include intentionally dropping network at each state transition.

---

# Phase 8 — Multi-client / multi-SIM routing

This phase expands the already-proven single-phone/single-SIM path. Do not begin it before Phases 5–7 are stable.

Implement:

- priorities;
- route eligibility;
- weights/round-robin for equal priority;
- per-client isolation;
- gateway slot resolution;
- SIM change protection;
- safe fallback.

Acceptance:

```text
Client A always uses configured A routes.
Client B cannot use A routes.
Dual-SIM phone sends through explicitly selected subscription.
Offline route falls back only when safe.
```

---

# Phase 9 — Rate limits and quotas

Implement:

- API rate limit;
- client SMS rate;
- SIM SMS rate;
- daily limits;
- billing-cycle limits;
- warnings;
- pause behavior.

Acceptance:

- limits hold under concurrent requests;
- restart does not reset durable billing-cycle usage;
- quota exhausted SIM leaves routing eligibility.

---

# Phase 10 — Usage and billing foundation

Implement:

- usage events;
- daily summaries;
- billing cycle summaries;
- provider account costs;
- SIM fixed/usage costs;
- client rate plans;
- provider invoice records.

Acceptance:

- a message with 3 parts creates 3 segments of usage;
- costs remain historically stable even if rate changes later;
- provider account/SIM invoice relationship is visible.

---

# Phase 11 — Webhooks and external integration readiness

Implement:

- client webhooks;
- signed events;
- delivery retry;
- API documentation;
- API-key scopes;
- audit.

Acceptance:

- external test application can send an SMS and receive delivery state by webhook without accessing admin DB.

At this point the future CRM can integrate cleanly.

---

# Phase 12 — CRM integration

Begin only after the standalone SMS platform is proven.

Implement in `crm.regantis.technology`:

- client/domain SMS configuration;
- dedicated SIM assignment;
- dedicated Android gateway assignment;
- provider/account references needed for client administration;
- client limits;
- pricing/billing configuration;
- SMS enable/disable;
- API integration with `sms.regantis.technology`;
- mapping between CRM client/domain and SMS server client ID;
- client-facing status/usage where required.

Default production mapping:

```text
1 client/domain → 1 SIM → 1 Android phone
```

The CRM must call the SMS server API; it must not directly manipulate SMS server transport tables.

Acceptance:

- a CRM client/domain can be configured with its dedicated SIM/gateway;
- CRM sends through `sms.regantis.technology`;
- SMS server uses the correct dedicated physical route;
- tenant boundaries are preserved;
- disabling SMS in CRM prevents new sends for that client;
- technical delivery history remains available from the SMS service.

---

# Phase 13 — Production hardening

Before production:

- full permission/device matrix;
- TLS check;
- admin 2FA decision;
- backup/restore test;
- Redis restart test;
- MariaDB restart test;
- Node restart test;
- Apache restart test;
- phone reboot test;
- phone offline test;
- SIM removal test;
- dual-SIM test;
- server/phone clock skew;
- API load test;
- queue burst test;
- duplicate-send chaos tests;
- log redaction review;
- message-body encryption;
- retention purge;
- operator contract/traffic rules documented per SIM;
- emergency kill switch verified.

---

# 51. Test matrix

Every meaningful module update should preserve these tests.

## API

- valid API key;
- invalid API key;
- revoked API key;
- client suspended;
- invalid E.164;
- empty message;
- oversized message;
- duplicate idempotency key same payload;
- duplicate idempotency key different payload;
- rate limited;
- no route;
- successful queue.

## Gateway

- valid credential;
- invalid credential;
- revoked credential;
- wrong protocol version;
- heartbeat;
- timeout;
- reconnect;
- screen off;
- Wi-Fi → mobile data;
- mobile data → Wi-Fi;
- device reboot;
- app process killed;
- manual force-stop behavior documented.

## SIM

- one SIM;
- dual SIM;
- correct subscription;
- SIM removed;
- SIM swapped;
- SIM disabled;
- airplane mode;
- radio unavailable.

## SMS

- short GSM-compatible;
- Romanian diacritics;
- Unicode;
- emoji;
- multipart;
- sent success;
- send failure;
- partial part failure;
- delivery success;
- no delivery report.

## Recovery / duplicate protection

- server dies before dispatch;
- server dies after dispatch command;
- socket dies before device ACK;
- socket dies after ACK;
- socket dies after actual SMS send;
- server restarts before sent event;
- Redis flush;
- phone reboots after local persistence;
- repeated API request;
- repeated WS command.

## Tenant isolation

- Client A cannot list Client B messages;
- Client A key cannot request Client B route;
- gateway cannot select another client's SIM;
- admin-only fields stay admin-only.

## Billing

- single segment;
- multipart;
- failed message no charge according to configured accounting rule;
- changed rate does not rewrite old usage;
- custom billing cycle;
- included quota exhausted;
- overage.

---

# 52. First production hardware acceptance test

Before calling a phone model supported, run at least:

1. install signed APK;
2. provision;
3. complete SMS role/permission setup;
4. keep screen off for several hours;
5. confirm heartbeats;
6. send at intervals;
7. send multipart Unicode;
8. reboot;
9. reconnect automatically;
10. remove/reinsert SIM;
11. switch Wi-Fi/mobile data;
12. test charger/discharger state;
13. send through every SIM slot;
14. confirm no slot mix-up;
15. confirm delivery callbacks;
16. run a controlled batch within contract limits.

Maintain a supported-device list after testing.

---

# 53. Initial API example

Create a client:

```text
Client: EngineParts test
```

Create an API key scoped to that client.

Call:

```http
POST https://sms.regantis.technology/api/v1/messages
Authorization: Bearer sms_live_xxxxxxxxx
Idempotency-Key: 54e7d621-...
Content-Type: application/json
```

```json
{
  "to": "+40722123456",
  "message": "Regantis SMS test",
  "reference": "manual-test-001"
}
```

Expected initial response:

```json
{
  "id": "sms_01...",
  "status": "queued"
}
```

Final lifecycle visible in admin:

```text
queued
→ assigned
→ accepted
→ sending
→ sent
→ delivery_pending
→ delivered
```

---

# 54. Initial real deployment example

```text
Provider:
Vodafone

Provider account:
Regantis Vodafone Business

SIM:
+40 7xx xxx xxx

Gateway:
GW-000001
Samsung Android phone

Gateway slot:
slot 0 → above SIM

Client:
Regantis Test

Route:
Regantis Test → above SIM / priority 10
```

This single route is enough to prove the system.

Only after this is stable should additional clients/SIMs be added.

---

# 55. CRM integration — intentionally near the end

The CRM is deliberately not part of the first implementation.

Do not connect `crm.regantis.technology` until:

```text
single provider/operator works
single SIM works
single Android phone works
API send works
sent callbacks work
delivery callbacks work
restart/reconnect reconciliation works
duplicate protection works
```

Only after the standalone transport is stable should CRM integration begin.

Final architecture:

```text
crm.regantis.technology
         │
         ├── client/domain
         ├── SMS enabled/configuration
         ├── assigned dedicated SIM
         ├── assigned dedicated Android gateway
         ├── operator/account metadata needed by CRM
         ├── limits / pricing / invoice configuration
         └── client-facing SMS settings
                  │
                  │ HTTPS API
                  ▼
sms.regantis.technology
                  │
         queue / routing / execution
                  │
                  ▼
          Android phone + SIM
```

Default intended business mapping:

```text
1 CRM client/domain
        ↓
1 dedicated SIM
        ↓
1 dedicated Android phone/gateway
```

The CRM becomes the business-level source of configuration for real clients/domains.

The SMS server remains authoritative for technical execution data such as:

- gateway connectivity;
- runtime SIM-slot mapping;
- queue state;
- attempts;
- Android result codes;
- sent/delivered events;
- reconciliation state;
- technical transport logs;
- actual SMS segment usage.

The CRM should integrate through the SMS HTTPS API and dedicated administrative/integration endpoints.

Do not make the CRM write directly into the SMS server database.

Recommended integration pattern:

```text
CRM
  ↓ authenticated HTTPS
SMS API
  ↓
SMS technical database / queue / gateways
```

The CRM may retain its own business references and map them to SMS server identifiers.

Do not make `sms_client.client_id` depend on CRM customer IDs. Use an explicit mapping/external reference.

This separation lets the SMS service continue operating even while CRM code is being deployed or maintained.

# 56. Future hardware expansion

The central platform should not be hard-coded to Android forever.

Future gateway types may include:

```text
ANDROID
LINUX_MODEM
MODEM_BANK
```

Abstract gateway capability:

```text
send SMS
report sent
report delivered
report status
report SIM slots
reconcile attempts
```

Do not implement modem-bank support now.

But keep `gateway_type` extensible so the database/API does not require redesign later.

---

# 57. Future inbound SMS

Potential later flow:

```text
recipient/customer
     ↓ SMS reply
physical SIM
     ↓
Android default SMS gateway
     ↓
sms.regantis.technology
     ↓
client webhook / CRM
```

If added, create separate inbound message tables/events.

Do not mix outbound delivery reports with inbound message content.

Inbound is explicitly a future phase.

---

# 58. Things a new chat must NOT redesign

Unless explicitly instructed by the user, do not:

- move the SMS execution API to `crm.regantis.technology`;
- require the CRM for the initial standalone sending/test path;
- start CRM integration before the single-phone/single-SIM path is proven;
- add Twilio/Infobip/SMSLink;
- switch to SMPP;
- create a new VPS without a demonstrated need;
- make SIM equal gateway;
- make provider equal SIM;
- make provider account equal provider;
- use Android default SIM selection for dual-SIM dispatch;
- treat Redis as message source of truth;
- blindly retry an uncertain message;
- count API messages instead of actual SMS segments;
- couple SMS client IDs to CRM customer IDs;
- build React Native gateway instead of native Kotlin without a strong reason;
- remove idempotency/reconciliation to simplify code.

---

# 59. Things that can be simplified for the first milestone

The first SMS does not need:

- client invoicing;
- provider invoice upload;
- webhooks;
- complex route weights;
- admin 2FA;
- batch campaigns;
- inbound SMS;
- MDM;
- multiple Node processes;
- modem banks.

But the schema/protocol must not create a dead end for these later features.

---

# 60. Definition of MVP

The first MVP is intentionally based on **one operator/provider + one provider account + one SIM + one Android phone**.

The MVP is complete when all of the following work:

```text
1. sms.regantis.technology online with HTTPS.
2. Node server, MariaDB and Redis healthy.
3. Standalone admin login.
4. Create provider/provider account/SIM/client/gateway/route.
5. Provision Android phone from QR.
6. Phone stays connected with screen off.
7. API key sends message using POST /api/v1/messages.
8. Server selects configured client SIM.
9. Correct SIM slot sends the message.
10. Recipient receives the SMS.
11. Android sent result reaches server.
12. Delivery result reaches server when network supports it.
13. Multipart SMS records actual segments.
14. API idempotency prevents duplicate API sends.
15. Gateway reconnect reconciliation prevents unsafe automatic duplicates.
16. Offline gateway handling works.
17. Multi-SIM selection works.
18. Usage counted by segment.
19. Admin can view message timeline.
20. Restart of Node/Redis/phone does not lose durable message state.
```

---

# 61. Definition of production-ready v1

Production-ready v1 additionally requires:

```text
multi-client tenant isolation
multiple SIM routes
safe fallback
quotas
rate limits
API scopes
gateway revocation
operator account separation
usage accounting
webhook support
encrypted message content
retention policy
audit log
backup/restore test
production monitoring
kill switches
supported Android device matrix
operator contract rules recorded per SIM/account
```

---

# 62. Suggested first development chat

When starting implementation from this file, the first chat should work only on:

```text
PHASE 0 + PHASE 1
```

Deliverables:

- exact cPanel/server setup;
- exact filesystem paths after inspecting the server configuration supplied by user;
- Node project skeleton;
- package.json;
- configuration;
- initial SQL migrations;
- health endpoints;
- MariaDB connection;
- Redis connection;
- service/reverse-proxy configuration;
- a test command proving `https://sms.regantis.technology/health/ready`.

Do not begin Android until the server foundation is running.

Do not begin CRM integration during the initial implementation chats. The near-term target remains exactly:

```text
1 provider/operator
1 account/contract
1 SIM
1 Android phone
1 test route
```

---

# 63. Recommended development handoff format after every chat

Every development chat should end with a handoff containing:

```text
1. Scope completed
2. Files added
3. Files changed
4. Database migrations added/applied
5. Server commands run
6. Current endpoints
7. Current test results
8. Known issues
9. Decisions made
10. Exact next task
```

If this canonical plan itself needs to change, update this file and increment its version/date.

New chats should receive:

```text
REGANTIS_SMS_IMPLEMENTATION_PLAN.md
+
latest development handoff
+
latest source archive if applicable
```

---

# 64. Current state at the time of this document

Completed:

```text
Architecture discussion
Hosting decision
Standalone hostname decision
Physical SIM transport decision
Multi-client/multi-provider/multi-SIM model
Initial technology choices
Reliability model
Implementation sequence
```

Not yet implemented:

```text
subdomain/application
database
Redis setup
Node backend
admin UI
Android gateway
API
single test route
multi-client routing
billing
CRM integration
```

Immediate implementation target:

```text
one provider/operator
one provider account
one SIM
one Android phone
one test client/route
```

CRM integration is intentionally deferred until near the end.

There is currently no need to preserve legacy SMS code because no implementation has started.

---

# 65. Current canonical architecture summary

```text
                         sms.regantis.technology
                                  │
                     ┌────────────┴────────────┐
                     │                         │
                 HTTPS API                  Admin UI
                     │                         │
                     └────────────┬────────────┘
                                  ▼
                           Node.js SMS Core
                                  │
                 ┌────────────────┼────────────────┐
                 │                │                │
              MariaDB           Redis          WebSocket
                 │                │                │
           durable state      execution         gateways
                                                   │
                        ┌──────────────────────────┼─────────┐
                        ▼                          ▼         ▼
                   Android GW 1              Android GW 2   ...
                        │                          │
                     SIM A                       SIM B
                        │                          │
                  Provider account A        Provider account B
                        │                          │
                    Vodafone                    Orange
                        │                          │
                        └──────────────┬───────────┘
                                       ▼
                                  SMS recipients
```

The future CRM is external to this diagram and will connect through the HTTPS API.

For the first real test, only one branch exists:

```text
sms.regantis.technology
        ↓
Android GW 1
        ↓
SIM 1
        ↓
Provider 1
        ↓
Recipient
```

Near the end, CRM becomes the business configuration layer for client/domain → dedicated SIM → dedicated Android gateway assignments.

---

# 66. Technical references verified during architecture review

Implementation should re-check current Android/cPanel documentation when coding against a newer SDK/version.

Key verified platform facts as of 2026-08-27:

1. Current Android `android.telephony.SmsManager` supports:
   - `sendTextMessage`;
   - `sendMultipartTextMessage`;
   - sent `PendingIntent`;
   - delivery `PendingIntent`;
   - `divideMessage`;
   - subscription-specific `createForSubscriptionId`.

2. `SmsManager.getDefault()` and `getSmsManagerForSubscriptionId()` are deprecated in modern API levels in favor of obtaining `SmsManager` from `Context` and using `createForSubscriptionId()`.

3. `SEND_SMS` is currently documented by Android as a dangerous, hard-restricted permission.

4. Android 14+ requires foreground services to declare appropriate service types and permissions.

5. Android provides `remoteMessaging` as a foreground-service type for messaging/relay use cases.

6. Android 15 places time limits on `dataSync` foreground services, so the permanent gateway connection should not be built as an endless `dataSync` service.

7. Android Doze/App Standby can restrict normal background network activity. Dedicated gateways therefore need the foreground-service approach and device battery configuration, plus real-device testing.

8. Current cPanel Application Manager documentation includes Node.js 22 support and Passenger application deployment on supported systems.

Primary documentation reviewed:

```text
Android Developers — android.telephony.SmsManager
Android Developers — Manifest.permission
Android Developers — SubscriptionManager
Android Developers — Foreground service types
Android Developers — Foreground service restrictions
Android Developers — Doze/App Standby
Android Open Source Project — restricted runtime permissions
cPanel & WHM — Application Manager / Passenger Applications
```

---

# 67. Final implementation principle

The central rule for the entire system is:

```text
The server owns intent, routing, durable state and accounting.
The Android gateway owns the physical attempt.
The mobile SIM/operator owns actual network transport.
```

Never blur those responsibilities.

For every outbound SMS the system must always be able to answer:

```text
Which client requested it?
Which API request/idempotency key created it?
Which route selected it?
Which physical SIM attempted it?
Which provider account pays for that SIM?
Which Android gateway/slot sent it?
How many actual SMS segments were used?
What did Android report?
Was delivery confirmed?
What will be billed?
Can we safely retry it?
```

If the implementation can answer those questions reliably, the architecture is doing its job.

---

**END OF CANONICAL IMPLEMENTATION PLAN**
