Inbound SMS
Rent a dedicated phone number and receive all incoming SMS in real-time. Agile Telecom hosts the SIM and forwards messages to you via email or webhook—perfect for customer support lines, appointment reminders, or automated responses.
Delivery Methods
Choose how you want to receive messages:
Email Delivery
Messages arrive in your inbox automatically from smsin@agiletelecom.com with the subject "Agile Telecom -- SMS received".
| Field | Format | Example |
|---|---|---|
| From | Sender's phone number | +393301234567 |
| To | Your rented SIM number | +393331234333 |
| Date/Time | YYYYMMDDHHMMSS | 20260408143052 |
| Body | SMS text | News ON |
Email is ideal for low-volume messages or integration with email-based systems.
HTTP POST Webhook
Messages post to your webhook URL in real-time. Choose this for automation, databases, and immediate processing.
POST Request Format
Your webhook receives a POST with these form parameters:
| Parameter | Description | Example |
|---|---|---|
| originator | Sender's phone number | +393301234567 |
| destination | Your SIM number | +393331234333 |
| date_time | Reception timestamp (YYYYMMDDHHMMSS) | 20260408143052 |
| text | SMS message body | News ON |
Required Response
Your server MUST respond with exactly:
+OK
If you don't respond with +OK within 30 seconds, Agile Telecom treats the delivery as failed and retries after 15 minutes. After 3 failed attempts, the message is discarded. Keep your webhook fast—offload heavy processing to background jobs.
Webhook Implementation Examples
- Python (Flask)
- Node.js (Express)
- PHP
- Java (Spring Boot)
- C# (ASP.NET)
- Go (net/http)
- Ruby (Sinatra)
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhook/sms', methods=['POST'])
def receive_sms():
# Extract parameters
originator = request.form.get('originator')
destination = request.form.get('destination')
date_time = request.form.get('date_time')
text = request.form.get('text')
# Process the message (async in background)
print(f"SMS from {originator}: {text}")
# Save to database
save_to_database(originator, destination, date_time, text)
# CRITICAL: respond with +OK immediately
return '+OK'
def save_to_database(originator, destination, date_time, text):
# Your database logic here
pass
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
**Deploy with Gunicorn (production):**
```bash
pip install flask gunicorn
gunicorn -w 4 -b 0.0.0.0:5000 app:app
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.urlencoded(\{ extended: false \}));
app.post('/webhook/sms', (req, res) => \{
const \{ originator, destination, date_time, text \} = req.body;
// Respond immediately with +OK (CRITICAL)
res.send('+OK');
// Process message async
processMessage(originator, destination, date_time, text);
\});
async function processMessage(originator, destination, date_time, text) \{
console.log(`SMS from $\{originator\}: $\{text\}`);
// Save to database
await saveToDatabase(originator, destination, date_time, text);
\}
async function saveToDatabase(originator, destination, date_time, text) \{
// Your database logic here
\}
app.listen(5000, () => console.log('Listening on port 5000'));
Run with PM2 (production):
npm install express body-parser pm2
pm2 start app.js -i max
<?php
// webhook-sms.php
// Extract POST parameters
$originator = isset($_POST['originator']) ? $_POST['originator'] : '';
$destination = isset($_POST['destination']) ? $_POST['destination'] : '';
$date_time = isset($_POST['date_time']) ? $_POST['date_time'] : '';
$text = isset($_POST['text']) ? $_POST['text'] : '';
// CRITICAL: respond with +OK immediately
header('Content-Type: text/plain');
echo '+OK';
// Process the message asynchronously
logMessage($originator, $destination, $date_time, $text);
function logMessage($originator, $destination, $date_time, $text) \{
// Log to file or database
$log = sprintf(
"[%s] From: %s, To: %s, Text: %s\n",
$date_time,
$originator,
$destination,
$text
);
file_put_contents('/var/log/sms.log', $log, FILE_APPEND);
// Or save to database
// saveToDatabase($originator, $destination, $date_time, $text);
\}
?>
Run with PHP (production):
php -S 0.0.0.0:5000
# Or use: supervisor with php-fpm
package com.example.sms;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import org.springframework.scheduling.annotation.Async;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SpringBootApplication
public class SmsWebhookApp \{
public static void main(String[] args) \{
SpringApplication.run(SmsWebhookApp.class, args);
\}
\}
@RestController
@RequestMapping("/webhook")
class SmsController \{
private static final Logger logger = LoggerFactory.getLogger(SmsController.class);
@PostMapping("/sms")
public String receiveSms(
@RequestParam String originator,
@RequestParam String destination,
@RequestParam String date_time,
@RequestParam String text) \{
// Respond immediately with +OK (CRITICAL)
processMessageAsync(originator, destination, date_time, text);
return "+OK";
\}
@Async
public void processMessageAsync(String originator, String destination,
String date_time, String text) \{
logger.info("SMS from \{\}: \{\}", originator, text);
// Save to database
// saveToDatabase(originator, destination, date_time, text);
\}
\}
application.properties:
server.port=5000
spring.task.execution.pool.core-size=10
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
[ApiController]
[Route("webhook")]
public class SmsController : ControllerBase \{
private readonly ILogger<SmsController> _logger;
private readonly IBackgroundTaskQueue _taskQueue;
public SmsController(ILogger<SmsController> logger, IBackgroundTaskQueue taskQueue) {
_logger = logger;
_taskQueue = taskQueue;
\}
[HttpPost("sms")]
public IActionResult ReceiveSms(
[FromForm] string originator,
[FromForm] string destination,
[FromForm] string date_time,
[FromForm] string text) \{
_logger.LogInformation($"SMS from \{originator\}: \{text\}");
// Queue async processing
_taskQueue.QueueBackgroundWorkItem(async token => \{
await ProcessMessageAsync(originator, destination, date_time, text);
\});
// CRITICAL: respond with +OK immediately
return Ok("+OK");
\}
private async Task ProcessMessageAsync(string originator, string destination,
string date_time, string text) \{
// Save to database
// await _dbContext.SaveSmsAsync(originator, destination, date_time, text);
\}
\}
Startup.cs:
public void ConfigureServices(IServiceCollection services) \{
services.AddControllers();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
\}
package main
import (
"fmt"
"log"
"net/http"
)
func receiveSMS(w http.ResponseWriter, r *http.Request) \{
// Parse POST parameters
if err := r.ParseForm(); err != nil \{
http.Error(w, "Bad request", http.StatusBadRequest)
return
\}
originator := r.FormValue("originator")
destination := r.FormValue("destination")
dateTime := r.FormValue("date_time")
text := r.FormValue("text")
log.Printf("SMS from %s: %s\n", originator, text)
// Respond with +OK immediately (CRITICAL)
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "+OK")
// Process message asynchronously
go processMessage(originator, destination, dateTime, text)
\}
func processMessage(originator, destination, dateTime, text string) \{
// Save to database or log
log.Printf("Processing: %s from %s at %s\n", text, originator, dateTime)
\}
func main() \{
http.HandleFunc("/webhook/sms", receiveSMS)
log.Println("Listening on :5000")
log.Fatal(http.ListenAndServe(":5000", nil))
\}
Run:
go run main.go
require 'sinatra'
require 'json'
set :port, 5000
set :bind, '0.0.0.0'
post '/webhook/sms' do
# Extract POST parameters
originator = params['originator']
destination = params['destination']
date_time = params['date_time']
text = params['text']
puts "SMS from #\{originator\}: #\{text\}"
# CRITICAL: respond with +OK immediately
# (this happens before the block ends)
status 200
headers['Content-Type'] = 'text/plain'
body '+OK'
# Process message asynchronously
Thread.new do
save_to_database(originator, destination, date_time, text)
end
end
def save_to_database(originator, destination, date_time, text)
# Your database logic here
puts "Saved: #\{originator\} -> #\{text\}"
end
__END__
Run with Puma (production):
gem install sinatra puma
puma -t 5:10 -p 5000
## Best Practices
**Respond Fast**
Return `+OK` within 1 second. Process the message in a background queue, not in the webhook handler.
**Handle Duplicates**
Agile Telecom may retry failed deliveries. Store a hash of the originator + timestamp to detect duplicates and avoid processing the same message twice.
**Log Everything**
Keep a log of all received messages for debugging and compliance. Include the timestamp, originator, and response time.
**Monitor Failures**
If delivery fails 3 times, the message is discarded. Monitor your webhook uptime and alert on consecutive failures.
**Example: Duplicate Detection (Python)**
```python
import hashlib
from datetime import datetime, timedelta
processed_messages = {} # In production, use Redis or a database
@app.route('/webhook/sms', methods=['POST'])
def receive_sms():
originator = request.form.get('originator')
date_time = request.form.get('date_time')
text = request.form.get('text')
# Create unique key
message_hash = hashlib.md5(f"{originator}{date_time}".encode()).hexdigest()
# Check for duplicate (same sender in last 5 minutes)
if message_hash in processed_messages:
return '+OK' # Already processed
# Mark as processed
processed_messages[message_hash] = datetime.now()
# Cleanup old entries
cutoff = datetime.now() - timedelta(minutes=5)
processed_messages = {k: v for k, v in processed_messages.items() if v > cutoff}
# Process new message
save_to_database(originator, request.form.get('destination'), date_time, text)
return '+OK'
What's next?
- SMPP Protocol – Persistent connection protocol for high-volume messaging
- Credit Check – Monitor your account balance
- MNP Lookup – Query mobile network information