The Melrose Labs SMPP Router now includes External Logic Integration (ELI)—a powerful capability that allows messaging engineers and developers to introduce real-time logic into message routing. A practical use case: blocking messages based on the source address.
In enterprise and telecom environments, managing who can submit SMS from which source address is essential for:
- Preventing abuse and spam
- Enforcing routing policies
- Protecting commercial messaging channels
With ELI, every inbound SubmitSM request can be evaluated against your own policies by triggering an HTTP POST to an external service that you control.
Use Case: Block Barred Source Addresses
Let’s walk through an example where certain source addresses are not allowed to submit messages. These addresses are stored in an SQLite database and matched using shell-style wildcard patterns. This example can be extended to block messages based on other fields, include message content.
The ELI server is implemented in Go and listens on port 8380. Upon receiving a request, it checks the sourceaddr field of the SMPP SubmitSM against a list of blocked patterns and responds accordingly.
Example: ELI Blocking Server in Go
Latest source code for eli_blocking.go can also be found at GitHub - melroselabs/smpp-router: SMPP Router example configurations
// External Logic Integration (ELI) support script for SMPP Router
// (c) 2025 Melrose Labs Ltd. All rights reserved.
//
// To run this program, follow these steps:
//
// 1. Install Go from https://golang.org/dl/
//
// 2. Install SQLite3 development headers:
// - macOS: `xcode-select --install`
// - Debian/Ubuntu: `sudo apt-get install build-essential libsqlite3-dev`
// - Windows: Use WSL or install a C compiler like TDM-GCC
//
// 3. Initialise a Go module and install the SQLite3 driver:
// go mod init eli_blocking
// go get github.com/mattn/go-sqlite3@latest
//
// 4. Create the SQLite3 database:
// sqlite3 barred.db <<EOF
// CREATE TABLE barred_srcaddrs (pattern TEXT NOT NULL);
// INSERT INTO barred_srcaddrs (pattern) VALUES ('blocked_*'), ('spam_sender');
// EOF
//
// 4a. To add or remove barred source addresses:
// Add: sqlite3 barred.db "INSERT INTO barred_srcaddrs (pattern) VALUES ('new_pattern');"
// Remove: sqlite3 barred.db "DELETE FROM barred_srcaddrs WHERE pattern = 'new_pattern';"
//
// Note: Restart the server to reload the updated pattern list.
//
// 5. Run the server:
// go run eli_blocking.go
//
// 6. Test with curl:
// curl -X POST http://localhost:8380/preroute/ -H "Content-Type: application/json" -d '{"sourceaddr":"spam_sender"}'
//
// Expected output:
// {"action": "reject", "reason": "barred source address"}
//
// curl -X POST http://localhost:8380/preroute/ -H "Content-Type: application/json" -d '{"sourceaddr":"allowed_user"}'
//
// Expected output:
// {"action": "accept"}
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"path/filepath"
"sync/atomic"
"time"
_ "github.com/mattn/go-sqlite3"
)
var (
// Atomic counter to ensure that the operation is thread-safe
connectionCount int64
msgId int64
db *sql.DB
barredPatterns []string
)
type SubmitSMMessage struct {
SourceAddr string `json:"sourceaddr"`
}
func loadBarredPatterns() {
rows, err := db.Query("SELECT pattern FROM barred_srcaddrs")
if err != nil {
log.Fatalf("Failed to load barred patterns: %v", err)
}
defer rows.Close()
for rows.Next() {
var pattern string
if err := rows.Scan(&pattern); err != nil {
log.Printf("Failed to scan pattern: %v", err)
continue
}
barredPatterns = append(barredPatterns, pattern)
}
}
func isBarredSourceAddr(sourceAddr string) bool {
for _, pattern := range barredPatterns {
matched, err := filepath.Match(pattern, sourceAddr)
if err != nil {
log.Printf("Invalid pattern %q: %v", pattern, err)
continue
}
if matched {
return true
}
}
return false
}
func main() {
var err error
db, err = sql.Open("sqlite3", "./barred.db")
if err != nil {
log.Fatalf("Failed to open DB: %v", err)
}
defer db.Close()
loadBarredPatterns()
// Setup HTTP server
http.HandleFunc("/preroute/", handlerPreRoute) // reject message decision
go func() {
for {
time.Sleep(1 * time.Second)
connections := atomic.SwapInt64(&connectionCount, 0)
fmt.Printf("ELI connections per second: %d\n", connections)
}
}()
// Start the server
fmt.Println("External Logic Integration server is starting on port 8380...")
if err := http.ListenAndServe(":8380", nil); err != nil {
fmt.Println("Error starting server:", err)
}
}
func handlerPreRoute(w http.ResponseWriter, r *http.Request) {
atomic.AddInt64(&connectionCount, 1)
w.Header().Set("Content-Type", "application/json")
var req SubmitSMMessage
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&req)
if err != nil {
http.Error(w, `{"error": "Invalid JSON"}`, http.StatusBadRequest)
return
}
if isBarredSourceAddr(req.SourceAddr) {
fmt.Fprint(w, `{"action": "reject", "reason": "barred source address"}`)
return
}
fmt.Fprint(w, `{"action": "accept"}`)
}
Getting Started
To use ELI with an existing SMPP Router for blocking messages, follow these simple steps:
1. Install prerequisites: Go, SQLite3 development libraries
2. Create the database:
sqlite3 barred.db <<EOF
CREATE TABLE barred_srcaddrs (pattern TEXT NOT NULL);
INSERT INTO barred_srcaddrs (pattern) VALUES ('blocked_*'), ('spam_sender');
EOF
3. Run the server:
go run eli_blocking.go
4. Test with curl:
curl -X POST http://localhost:8380/preroute/ \
-H "Content-Type: application/json" \
-d '{"sourceaddr":"spam_sender"}'
Expected output:
{"action": "reject", "reason": "barred source address"}
- Configure SMPP Router to use ELI by adding the following block to smpprouter.conf and restart SMPP Router:
"eli": {
"enabled":true,
"url":"http://127.0.0.1:8380/preroute/",
"max_queue_size":10000,
"worker_count":32,
"worker_count_min":5
},
Smarter Messaging Control
By leveraging ELI in SMPP Router, organisations can implement dynamic, programmable policies without modifying the core routing logic. This results in greater flexibility, enhanced security, and better traffic governance.
For more details and to download SMPP Router, visit: SMPP Router - Melrose Labs