Inbound SMS
What is Inbound SMS
Rent a dedicated phone number to receive all incoming SMS in real-time. Agile Telecom hosts the SIM and forwards messages via email or webhook — ideal for customer support lines, appointment reminders, or automated responses.
Email Delivery
When email delivery is configured, incoming SMS messages are forwarded to your specified email address from smsin@agiletelecom.com.
Each email contains the following fields:
| Field | Description |
|---|---|
| From | Sender phone number (MSISDN) |
| To | Your dedicated number |
| Date/Time | Timestamp of message receipt |
| Body | SMS text content |
:::tip Simple Setup Email delivery requires no development — just configure your destination email address in the portal and start receiving messages immediately. :::
HTTP POST Webhook
For programmatic processing, configure an HTTP POST webhook. Agile Telecom will send incoming SMS data to your endpoint.
Request Parameters
| Parameter | Type | Description |
|---|---|---|
originator | string | Sender phone number (MSISDN) |
destination | string | Your dedicated number |
date_time | string | Timestamp of message receipt |
text | string | SMS text content |
Response Requirements
Your endpoint must respond with +OK in the response body. Any other response is treated as a failure.
| Setting | Value |
|---|---|
| Required response | +OK |
| Timeout | 30 seconds |
| Retry delay | 15 minutes |
| Max retries | 3 |
:::warning Response Required
If your endpoint does not respond with +OK within 30 seconds, the system will retry up to 3 times with a 15-minute delay between attempts. After 3 failures, the message is dropped.
:::
Webhook Implementation Examples
Python (Flask)
from flask import Flask, request
import threading
app = Flask(__name__)
def process_message(originator, destination, date_time, text):
"""Process inbound SMS asynchronously."""
print(f"From: {originator}, To: {destination}, Time: {date_time}")
print(f"Message: {text}")
# Add your business logic here
@app.route("/inbound-sms", methods=["POST"])
def inbound_sms():
originator = request.form.get("originator")
destination = request.form.get("destination")
date_time = request.form.get("date_time")
text = request.form.get("text")
# Process asynchronously to respond fast
thread = threading.Thread(
target=process_message,
args=(originator, destination, date_time, text)
)
thread.start()
return "+OK"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Node.js (Express)
const express = require("express");
const app = express();
app.use(express.urlencoded({ extended: true }));
function processMessage(originator, destination, dateTime, text) {
console.log(`From: ${originator}, To: ${destination}, Time: ${dateTime}`);
console.log(`Message: ${text}`);
// Add your business logic here
}
app.post("/inbound-sms", (req, res) => {
const { originator, destination, date_time, text } = req.body;
// Process asynchronously to respond fast
setImmediate(() => processMessage(originator, destination, date_time, text));
res.send("+OK");
});
app.listen(3000, () => console.log("Listening on port 3000"));
PHP
<?php
// inbound-sms.php
$originator = $_POST['originator'] ?? '';
$destination = $_POST['destination'] ?? '';
$date_time = $_POST['date_time'] ?? '';
$text = $_POST['text'] ?? '';
// Log the message
$logEntry = sprintf(
"[%s] From: %s, To: %s, Message: %s\n",
$date_time, $originator, $destination, $text
);
file_put_contents('inbound-sms.log', $logEntry, FILE_APPEND);
// Process your business logic here
// ...
// Required response
echo "+OK";
Java (Spring Boot)
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.CompletableFuture;
@RestController
public class InboundSmsController {
@PostMapping("/inbound-sms")
public String handleInboundSms(
@RequestParam String originator,
@RequestParam String destination,
@RequestParam("date_time") String dateTime,
@RequestParam String text) {
// Process asynchronously
CompletableFuture.runAsync(() -> {
System.out.printf("From: %s, To: %s, Time: %s%n", originator, destination, dateTime);
System.out.printf("Message: %s%n", text);
// Add your business logic here
});
return "+OK";
}
}
Go (net/http)
package main
import (
"fmt"
"net/http"
)
func processMessage(originator, destination, dateTime, text string) {
fmt.Printf("From: %s, To: %s, Time: %s\n", originator, destination, dateTime)
fmt.Printf("Message: %s\n", text)
// Add your business logic here
}
func inboundSmsHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
r.ParseForm()
originator := r.FormValue("originator")
destination := r.FormValue("destination")
dateTime := r.FormValue("date_time")
text := r.FormValue("text")
// Process asynchronously
go processMessage(originator, destination, dateTime, text)
fmt.Fprint(w, "+OK")
}
func main() {
http.HandleFunc("/inbound-sms", inboundSmsHandler)
fmt.Println("Listening on :8080")
http.ListenAndServe(":8080", nil)
}
C# (ASP.NET)
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("inbound-sms")]
public class InboundSmsController : ControllerBase
{
[HttpPost]
public IActionResult HandleInboundSms(
[FromForm] string originator,
[FromForm] string destination,
[FromForm(Name = "date_time")] string dateTime,
[FromForm] string text)
{
// Process asynchronously
_ = Task.Run(() =>
{
Console.WriteLine($"From: {originator}, To: {destination}, Time: {dateTime}");
Console.WriteLine($"Message: {text}");
// Add your business logic here
});
return Content("+OK");
}
}
Ruby (Sinatra)
require "sinatra"
def process_message(originator, destination, date_time, text)
puts "From: #{originator}, To: #{destination}, Time: #{date_time}"
puts "Message: #{text}"
# Add your business logic here
end
post "/inbound-sms" do
originator = params["originator"]
destination = params["destination"]
date_time = params["date_time"]
text = params["text"]
# Process asynchronously
Thread.new { process_message(originator, destination, date_time, text) }
"+OK"
end
Best Practices
Respond Fast
Your endpoint must return +OK within 1 second to avoid timeout issues. Offload all processing to background workers or async threads.
:::tip Performance
Always respond with +OK before processing the message. Use async patterns (threads, queues, background jobs) for business logic.
:::
Handle Duplicates
Due to retries, your webhook may receive the same message more than once. Implement idempotency using a message hash:
import hashlib
seen_messages = set()
def is_duplicate(originator, destination, date_time, text):
msg_hash = hashlib.sha256(
f"{originator}{destination}{date_time}{text}".encode()
).hexdigest()
if msg_hash in seen_messages:
return True
seen_messages.add(msg_hash)
return False
:::caution Production Use
The in-memory set() example above is for illustration only. In production, use a persistent store like Redis or a database with TTL-based expiration.
:::
Log Everything
Log every incoming message with timestamp, originator, and processing result. This is essential for debugging delivery issues and auditing.
Monitor Failures
Set up alerting for:
- Webhook response times exceeding 5 seconds
- Error rates above 1%
- Missing messages (gaps in expected sequences)