SMSc - SMS Service Center Application
SMS Center application built with Phoenix/Elixir, providing a centralized message queue and REST API for SMS routing and delivery.
Documentation
Core Documentation
- Complete Documentation Index - Start here for all documentation
- Configuration Reference - Complete configuration options
- API Reference - REST API documentation
- Operations Guide - Day-to-day operations and monitoring
- SMS Routing Guide - Route management and configuration
- Number Translation Guide - Number normalization and rewriting
- Performance Tuning - Optimization for different workloads
- Metrics Guide - Prometheus metrics and monitoring
- Troubleshooting Guide - Common issues and solutions
- CDR Schema - Call detail record format
Compliance Documentation
- ANSSI R226 Interception Compliance - French lawful interception technical specifications
Performance
- Benchmarks - Performance testing
Architecture Overview
SMS_C Core provides a protocol-agnostic message queue and REST API. External SMSC frontends (SMPP, IMS, SS7/MAP) connect as standalone gateways that communicate with the core via REST API.
Message Flow
Outbound Message Flow (MT - Mobile Terminated)
Inbound Message Flow (MO - Mobile Originated)
Key Features
1. Protocol-Agnostic Design
- SMS_C Core handles message persistence, routing, and API
- External frontends (SMPP, IMS, SS7/MAP) handle protocol-specific communication
- All frontends communicate via unified REST API
- Add new protocols without changing core
- Each can be scaled independently
2. Message Routing
- Dynamic routing engine with runtime configuration
- Prefix-based routing (calling/called numbers)
- SMSC and source type filtering (IMS/Circuit Switched/SMPP)
- Priority-based routing with weight-based load balancing
- Auto-reply routing and message drop capabilities
- Charging control per route
- Web UI for route management
- Real-time route updates without service interruption
📖 See SMS Routing Guide for comprehensive documentation
3. Retry Logic with Exponential Backoff
- Automatic retry on delivery failure
- Exponential backoff: 1min, 2min, 4min, 8min, etc.
- Configurable maximum retry attempts
- Message expiration handling
- Per-message retry tracking
Operations Guide
Access Points:
- REST API:
https://localhost:8443(orhttp://localhost:8080without TLS) - Control Panel:
https://localhost:8086 - API Documentation (Swagger UI):
https://localhost:8443/api/docs
Start External Frontends: Each protocol frontend is a standalone application. See individual frontend documentation for startup instructions.
Configuration
All configuration is managed directly in config/runtime.exs. No environment variables are used.
Core Configuration
No core application configuration environment variables are currently used. Server ports and hostnames are configured in config/runtime.exs:
- API Server: Port 8443 (HTTPS), listening on 0.0.0.0
- Control Panel: Port 80 (HTTP), listening on 0.0.0.0
Database Configuration
Database settings are configured in config/runtime.exs:
- Username:
omnitouch - Password:
omnitouch2024 - Hostname:
localhost - Port:
3306 - Database name:
smsc_new - Pool size:
1
Cluster Configuration
Cluster settings are configured in config/runtime.exs:
- Cluster nodes:
""(empty - no clustering by default) - DNS cluster query:
nil
Message Queue Configuration
Message queue settings are configured in config/runtime.exs:
- Dead letter time:
1440minutes (24 hours before message expires)
Charging Integration
Charging settings are configured in config/runtime.exs:
- URL:
http://localhost:2080/jsonrpc - Tenant:
mnc057.mcc505.3gppnetwork.org - Destination:
55512341234 - Source:
00101900000257 - Subject:
00101900000257 - Account:
00101900000257
SMS Routing Configuration
The SMS routing system uses dynamic, database-backed routes that can be managed via Web UI or configuration file. Routes are loaded from config/runtime.exs on first startup.
Configuration Example:
config :sms_c, :sms_routes, [
%{
called_prefix: "+44",
dest_smsc: "InternationalGW",
weight: 100,
priority: 100,
description: "UK International SMS",
enabled: true
},
%{
called_prefix: "1900",
dest_smsc: "PremiumGW",
charged: :yes,
priority: 50,
description: "US Premium Numbers",
enabled: true
}
]
Features:
- Prefix-based matching (calling/called numbers)
- Source SMSC and type filtering
- Priority and weight-based routing
- Auto-reply and drop capabilities
- Per-route charging control
- Runtime management via Web UI at
/routing
📖 See SMS Routing Guide for complete documentation, examples, and API reference.
REST API Endpoints
Message Queue Operations
Submit SMS (Create Message)
POST /api/messages
Content-Type: application/json
{
"source_msisdn": "+1234567890",
"destination_msisdn": "+0987654321",
"message_body": "Hello, World!",
"source_smsc": "web-app",
"dest_smsc": "smpp-provider", # Optional - routing engine assigns if null
"tp_dcs_character_set": "gsm7", # Optional: gsm7, 8bit, latin1, ucs2
"tp_dcs_coding_group": "general_data_coding",
"expires": "2025-10-17T10:30:00Z" # Optional - defaults to 24h from now
}
Required Fields:
destination_msisdn- Destination phone numbermessage_body- Message text contentsource_msisdn- Source phone numbersource_smsc- Source system identifier
Optional Fields:
dest_smsc- Destination gateway (routing engine assigns if not provided)source_imsi,dest_imsi- IMSI identifierstp_dcs_character_set- Character encoding (gsm7, 8bit, latin1, ucs2)tp_dcs_coding_group- DCS coding grouptp_dcs_compressed- Compression flag (boolean)tp_dcs_has_message_class- Message class flag (boolean)tp_dcs_message_class- Message class valuetp_user_data_header- User data header (map)message_part_number,message_parts- Multipart message fieldsexpires- Expiration timestamp (defaults to 24 hours)deliver_after- Delayed delivery timestampdeadletter,raw_data_flag,raw_sip_flag- Boolean flags
Response:
{
"status": "success",
"data": {
"id": 123,
"source_msisdn": "+1234567890",
"destination_msisdn": "+0987654321",
"dest_smsc": "smpp-provider",
"message_body": "Hello, World!",
"deliver_time": null,
"delivery_attempts": 0,
"expires": "2025-10-17T10:30:00Z",
"inserted_at": "2025-10-16T10:30:00Z"
}
}
Get Messages for SMSC
GET /api/messages/get_by_smsc?smsc=my-smsc-name
Returns all undelivered messages where:
destination_smscis null OR matches the provided SMSC name- Message is not expired
- Ready to send (deliver_after is null or in the past)
Response:
{
"status": "success",
"data": [
{
"id": 123,
"source_msisdn": "+1234567890",
"destination_msisdn": "+0987654321",
"message_body": "Hello",
"destination_smsc": "my-smsc-name",
"delivery_attempts": 0
}
]
}
List Messages with Optional SMSC Filtering
# List all messages in the queue
GET /api/messages
# List messages for specific SMSC (with header filtering)
GET /api/messages
SMSc: my-smsc-name
Without SMSc Header: Returns all messages in the queue regardless of delivery status or expiration.
With SMSc Header: Returns undelivered messages where:
dest_smscmatches the header value ORdest_smscis nulldeliver_timeis null (not yet delivered)deliver_afteris null or before/equal to current time (ready to deliver)expiresis after current time (not expired)- Ordered by insertion time (oldest first)
Note: The SMSc header approach allows external frontends to poll for their messages using the same endpoint pattern, with the header controlling the filtering behavior.
Response:
[
{
"id": 123,
"source_msisdn": "+1234567890",
"destination_msisdn": "+0987654321",
"message_body": "Hello, World!",
"dest_smsc": "my-smsc-name",
"deliver_time": null,
"delivery_attempts": 0,
"expires": "2025-10-17T10:30:00Z",
"inserted_at": "2025-10-16T10:30:00Z"
}
]
Get Single Message
GET /api/messages/{id}
Update Message
PATCH /api/messages/{id}
Content-Type: application/json
{
"status": "delivered",
"delivered_at": "2025-10-16T10:30:00Z"
}
Delete SMS
DELETE /api/messages/{id}
Handle Failed Delivery (Increment Retry Counter)
When a message delivery fails temporarily, increment the delivery attempt counter and schedule retry with exponential backoff.
Method 1: Using PUT (Recommended)
# Simple and semantic - PUT indicates updating delivery state
PUT /api/messages/{id}
Method 2: Using Explicit Endpoint
# Alternative explicit endpoint
POST /api/messages/{id}/increment_delivery_attempt
Both methods increment delivery_attempts and set exponential backoff delay via deliver_after:
| Attempt | Backoff Formula | Delay | Total Time |
|---|---|---|---|
| 1st | 2^1 minutes | 2 min | 2 min |
| 2nd | 2^2 minutes | 4 min | 6 min |
| 3rd | 2^3 minutes | 8 min | 14 min |
| 4th | 2^4 minutes | 16 min | 30 min |
| 5th | 2^5 minutes | 32 min | 1h 2min |
| 6th | 2^6 minutes | 64 min | 2h 6min |
Response:
{
"id": 123,
"delivery_attempts": 1,
"deliver_after": "2025-10-20T19:05:00Z",
"deliver_time": null,
"expires": "2025-10-21T19:03:00Z",
...
}
Note: Messages with future deliver_after are automatically filtered from GET requests until the backoff period expires.
Update Message (Partial Update)
For updating specific message fields (unchanged behavior):
PATCH /api/messages/{id}
Content-Type: application/json
{
"dest_smsc": "updated-gateway",
"status": "delivered"
}
Important: PUT and PATCH behave differently:
- PUT → Increments delivery attempts with backoff (no body required)
- PATCH → Performs partial field updates (body required)
Frontend Health Tracking
The SMS_C Core tracks the health and availability of external frontends through a registration system. This allows monitoring of frontend uptime, detecting failures, and maintaining historical availability data.
Note: Frontend registration is NOT used for message delivery or routing. Messages are routed based on the dest_smsc field. The registration system exists solely for health monitoring and operational visibility.
How Frontend Registration Works
Each external frontend (SMPP, IMS, SS7/MAP gateway) periodically sends a heartbeat registration to the SMS_C Core:
- Heartbeat Interval: Frontends should register every 30-60 seconds
- Expiration Time: Registrations expire after 90 seconds without an update
- Automatic State Management:
- New frontends create a new registration record
- Existing active frontends update their registration (extends expiration)
- Expired frontends that come back online create a new registration period
Frontend Registration Endpoints
Register/Update Frontend (Heartbeat)
POST /api/frontends
Content-Type: application/json
{
"frontend_name": "smpp-gateway-1",
"frontend_type": "SMPP",
"ip_address": "10.0.1.5",
"hostname": "smpp-gw-01",
"uptime_seconds": 3600,
"configuration": "{\"port\": 2775, \"system_id\": \"smpp_user\"}"
}
Required Fields:
frontend_name- Unique identifier for the frontend instancefrontend_type- Type of frontend (SMPP, IMS, MAP, etc.)
Optional Fields:
ip_address- IP address of the frontend (auto-detected from request source if not provided)hostname- Hostname of the frontend serveruptime_seconds- Seconds since the frontend startedconfiguration- JSON string with frontend-specific config
Note: If ip_address is not provided, the SMS_C Core will automatically use the source IP of the HTTP request. This works with both direct connections and proxied requests (via X-Forwarded-For header).
Response:
{
"id": 42,
"frontend_name": "smpp-gateway-1",
"frontend_type": "SMPP",
"ip_address": "10.0.1.5",
"hostname": "smpp-gw-01",
"uptime_seconds": 3600,
"status": "active",
"last_seen_at": "2025-10-20T10:30:00Z",
"expires_at": "2025-10-20T10:31:30Z",
"inserted_at": "2025-10-20T10:00:00Z"
}
List All Frontend Registrations
GET /api/frontends
Returns all frontend registrations (active and expired), ordered by most recent activity.
List Active Frontends Only
GET /api/frontends/active
Returns only currently active (non-expired) frontends.
Get Frontend Statistics
GET /api/frontends/stats
Returns summary statistics:
{
"active": 5,
"expired": 12,
"unique_frontends": 8
}
Get Frontend History
GET /api/frontends/history/{frontend_name}
Returns all historical registrations for a specific frontend, useful for analyzing uptime/downtime patterns.
Example:
GET /api/frontends/history/smpp-gateway-1
Get Specific Registration
GET /api/frontends/{id}
Implementation in External Frontends
External frontends should implement a background task that sends heartbeats:
Example (pseudocode):
import time
import requests
def send_heartbeat():
"""Send heartbeat every 30 seconds"""
while True:
try:
data = {
"frontend_name": "my-smpp-gateway",
"frontend_type": "SMPP",
"ip_address": get_local_ip(),
"hostname": get_hostname(),
"uptime_seconds": get_uptime()
}
response = requests.post(
"https://smsc-core:8443/api/frontends",
json=data,
timeout=5
)
if response.status_code in [200, 201]:
logger.debug("Heartbeat sent successfully")
else:
logger.error(f"Heartbeat failed: {response.status_code}")
except Exception as e:
logger.error(f"Heartbeat error: {e}")
time.sleep(30) # Send every 30 seconds
# Start heartbeat in background thread
threading.Thread(target=send_heartbeat, daemon=True).start()
Monitoring Frontend Health
Control Panel - The web UI at https://localhost:8086 shows:
- Currently active frontends
- Last seen timestamp for each frontend
- Uptime tracking
- Historical availability
API Queries:
# Get all active frontends
curl https://localhost:8443/api/frontends/active
# Check if specific frontend is up
curl https://localhost:8443/api/frontends/history/smpp-gateway-1 | jq '.[0].status'
# Get health statistics
curl https://localhost:8443/api/frontends/stats
Other Endpoints
Status
GET /api/status
Locations
GET /api/locations
POST /api/locations
GET /api/locations/{id}
PATCH /api/locations/{id}
DELETE /api/locations/{id}
SS7 Events
GET /api/ss7_events
POST /api/ss7_events
GET /api/ss7_events/{id}
PATCH /api/ss7_events/{id}
DELETE /api/ss7_events/{id}
MMS Message Queue
GET /api/mms_message_queues
POST /api/mms_message_queues
GET /api/mms_message_queues/{id}
PATCH /api/mms_message_queues/{id}
DELETE /api/mms_message_queues/{id}
Performance
The SMS_C Core delivers exceptional throughput using Mnesia for in-memory message storage with automatic archiving to SQL for long-term CDR retention.
Benchmark Results
Measured on Intel i7-8650U @ 1.90GHz (8 cores):
Message Insert Performance:
insert_message(with routing): 1,750 msg/sec (0.58ms avg latency)insert_message(simple): 1,750 msg/sec (0.57ms avg latency)- ~150 million messages per day capacity
Query Performance:
get_messages_for_smsc: 800 msg/sec (1.25ms avg)list_message_queues: Fast in-memory access- Memory usage: 62 KB per insert operation
Architecture
Storage Strategy:
- Active Messages: Stored in Mnesia (in-memory + disk) for ultra-fast access
- Message Archive: Automatically archived to SQL for long-term CDR storage
- Retention: Configurable retention period (default: 24 hours)
- No SQL bottleneck: All active message operations bypass SQL
Configuration
Message storage and retention configured in config/runtime.exs:
config :sms_c,
message_retention_hours: 24, # Archive messages older than 24 hours
batch_insert_batch_size: 100, # CDR batch size for SQL archiving
batch_insert_flush_interval_ms: 100 # CDR flush interval
For detailed tuning guidance, see: docs/PERFORMANCE_TUNING.md
Monitoring
Control Panel - Web UI at https://localhost:8086
- View message queue
- Submit test messages
- Manage SMS routing (see Routing Guide)
- Simulate routing decisions
- View system resources
- Track batch worker statistics
Batch Worker Statistics:
# Get current batch worker stats
SmsC.Messaging.BatchInsertWorker.stats()
Returns:
%{
total_enqueued: 10000,
total_flushed: 9900,
current_queue_size: 100,
last_flush_duration_ms: 45
}
Logs - Application logs written to stdout
# View logs in real-time
tail -f log/dev.log
Troubleshooting
Port Already in Use
# Find process using port
lsof -i :4000
# Kill the process
kill -9 <PID>
External Frontend Not Connecting
Symptoms: Messages stuck in queue, frontend logs show connection errors
Check:
- Verify
API_BASE_URLis correctly set in frontend - Check SMS_C Core is running and accessible
- Review network/firewall rules
- Verify frontend configuration
Solution:
# Test API connectivity from frontend
curl http://localhost:4000/api/status
# Restart frontend
export API_BASE_URL="http://localhost:4000"
# Start frontend application
Messages Not Being Delivered
Symptoms: Messages remain undelivered, retry attempts incrementing
Check:
- Frontend logs for send errors
- External network connectivity
- Frontend configuration (credentials, addresses)
- Message format compatibility
View failed messages:
# Get messages with retry attempts
curl https://localhost:8443/api/messages | jq '.data[] | select(.delivery_attempts > 0)'
High Message Latency
Symptoms: Messages taking longer than expected, queue backlog
Check:
- Frontend polling interval (may need to decrease for more frequent polling)
- Database performance
- Network latency to external systems
Monitor queue depth:
watch -n 5 'curl -s https://localhost:8443/api/messages | jq ".data | length"'