Skip to main content

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:

FieldDescription
FromSender phone number (MSISDN)
ToYour dedicated number
Date/TimeTimestamp of message receipt
BodySMS 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

ParameterTypeDescription
originatorstringSender phone number (MSISDN)
destinationstringYour dedicated number
date_timestringTimestamp of message receipt
textstringSMS text content

Response Requirements

Your endpoint must respond with +OK in the response body. Any other response is treated as a failure.

SettingValue
Required response+OK
Timeout30 seconds
Retry delay15 minutes
Max retries3

:::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)