Jasmin
Jasmin
Jasmin
Release 0.10.11s
Jookies LTD
1 Features 3
2 Getting started 5
3 Full contents 7
3.1 Architecture overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2 Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.3 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.4 RESTful API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.5 HTTP API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.6 SMPP Server API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.7 The message router . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.8 Interception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.9 Programming examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.10 Management CLI overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.11 Management CLI Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.12 Billing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
3.13 Messaging flows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
3.14 User FAQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
3.15 Developer FAQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
4 Links 111
5 License 113
i
ii
Jasmin Documentation, Release 0.10.11s
Jasmin is an open-source SMS Gateway with many enterprise-class features, Jasmin is built to be easily customized
to meet the specific needs of messaging exchange growing business.
Based on strong message routing algorithms, Jasmin provides flexibility to define rule based routing based on var-
ious criteria: sender ID, source, destination and many combinations. Auto reconnection and re-routing mechanism
managing peak hours or link failover for high availability services.
Jasmin is written in Python and Twisted framework for serving highly scalable applications, SMS message delivery
can be done through HTTP and SMPP protocols, intelligent routing can be configured in real-time through an API, cli
interface or a web backend1 .
Contents 1
Jasmin Documentation, Release 0.10.11s
2 Contents
CHAPTER 1
Features
3
Jasmin Documentation, Release 0.10.11s
4 Chapter 1. Features
CHAPTER 2
Getting started
5
Jasmin Documentation, Release 0.10.11s
Full contents
7
Jasmin Documentation, Release 0.10.11s
3.2 Support
The easiest way to get help with the project is to open an issue on Github.
The forum is also available for support.
We offer commercial support for Jasmin, commercial solution hosting, as well as remote and on-site consulting and
engineering.
You can contact us at [email protected] or raise a demand through our Helpdesk to learn more.
3.3 Installation
The Installation section is intended to get you up and running quickly with a simple SMS sending scenario through
HTTP API or SMPP Server API.
Jasmin installation is provided as rpm & deb Linux packages, docker image and pypi package.
Important: Jasmin needs a working RabbitMQ and Redis servers, more info in Prerequisites & Dependencies
below.
Jasmin requires Python 3 (Python 2 is no more supported) with a functioning pip module.
Depending on the Linux distribution you are using, you may need to install the following dependencies:
• RabbitMQ Server, Ubuntu package name: rabbitmq-server. RabbitMQ is used heavily by Jasmin as its core
AMQP.
• Redis Server, Ubuntu package name: redis-server. Redis is used mainly for mapping message ID’s when
receiving delivery receipts.
• header files and a static library for Python, Ubuntu package name: python-dev
• Foreign Function Interface library (development files), Ubuntu package name: libffi-dev
• Secure Sockets Layer toolkit - development files, Ubuntu package name: libssl-dev
• Twisted Matrix, Python Event-driven networking engine, Ubuntu package name: python-twisted
3.2. Support 9
Jasmin Documentation, Release 0.10.11s
3.3.2 Ubuntu
You have to install and setup RabbitMQ or Redis servers on same machine (Default configuration) or on separate
ones (Requires Jasmin configuration: /etc/jasmin/jasmin.cfg).
Once Jasmin installed, you may simply start the jasmind service:
Note: Many dependencies are installed from the Epel repository, please pay attention to activating this repository
before installing jasmin-sms-gateway package.
Note: Red Hat Enterprise Linux 8 & CentOS 8 and newer versions are supported.
You have to install and setup RabbitMQ or Redis servers on same machine (Default configuration) or on separate
ones (Requires Jasmin configuration: /etc/jasmin/jasmin.cfg).
Once Jasmin installed, you may simply start the jasmind service:
3.3.4 Pypi
Having another OS not covered by package installations described above ? using the Python package installer will be
possible, you may have to follow these instructions:
System user
Jasmin system service is running under the jasmin system user, you will have to create this user under jasmin group:
System folders
In order to run as a POSIX system service, Jasmin requires the creation of the following folders before installation:
/etc/jasmin
/etc/jasmin/resource
/etc/jasmin/store #> Must be owned by jasmin user
/var/log/jasmin #> Must be owned by jasmin user
Installation
3.3.5 Docker
Containers are ideal for microservice architectures and for environments that scale rapidly or release often, Here’s
more from Docker’s website.
Installing Docker
Before we get into containers, we’ll need to get Docker running locally. You can do this by installing the package for
your system (tip: you can find yours here).
Once that’s set up, you’re ready to start using Jasmin container !
3.3. Installation 11
Jasmin Documentation, Release 0.10.11s
Using docker-compose
services:
redis:
image: redis:alpine
restart: unless-stopped
rabbit-mq:
image: rabbitmq:alpine
restart: unless-stopped
jasmin:
image: jookies/jasmin:0.10
restart: unless-stopped
container_name: jasmin
volumes:
- /var/log/jasmin:/var/log/jasmin
ports:
- 2775:2775
- 8990:8990
- 1401:1401
depends_on:
- redis
- rabbit-mq
environment:
REDIS_CLIENT_HOST: redis
AMQP_BROKER_HOST: rabbit-mq
This command will pull latest jasmin v0.10, latest redis and latest rabbitmq images to your computer:
# docker images
REPOSITORY TAG IMAGE ID CREATED
˓→VIRTUAL SIZE
Note: You can play around with the docker-compose.yml to choose different versions, mounting the configs outside
the container, etc . . .
For the really impatient, if you want to give Jasmin a whirl right now and send your first SMS, you’ll have to connect
to Management CLI overview and setup a connection to your SMS-C, let’s assume you have the following SMPP
connection parameters as provided from your partner:
Note: In the next sections we’ll be heavily using jCli console, if you feel lost, please refer to Management CLI
overview for detailed information.
Connect to jCli console through telnet (telnet 127.0.0.1 8990) using jcliadmin/jclipwd default authentication param-
eters and add a new connector with an CID=DEMO_CONNECTOR:
Authentication required.
Username: jcliadmin
Password:
Welcome to Jasmin console
Type help or ? to list commands.
Session ref: 2
jcli : smppccm -a
(continues on next page)
3.3. Installation 13
Jasmin Documentation, Release 0.10.11s
You can check if the connector is bound to your provider by checking its log file (default to /var/log/jasmin/default-
DEMO_CONNECTOR.log) or through jCli console:
We’ll configure a default route to send all SMS through our newly created DEMO_CONNECTOR:
jcli : mtrouter -a
Adding a new MT Route: (ok: save, ko: exit)
> type defaultroute
jasmin.routing.Routes.DefaultRoute arguments:
connector
> connector smppc(DEMO_CONNECTOR)
> rate 0.00
> ok
Successfully added MTRoute [DefaultRoute] with order:0
4. Create a user
In order to use Jasmin’s HTTP API to send SMS messages, you have to get a valid user account, that’s what we’re
going to do below.
First we have to create a group to put the new user in:
jcli : group -a
Adding a new Group: (ok: save, ko: exit)
> gid foogroup
> ok
Successfully added Group [foogroup]
jcli : user -a
Adding a new User: (ok: save, ko: exit)
> username foo
> password bar
> gid foogroup
> uid foo
> ok
Successfully added User [foo] to Group [foogroup]
5. Send SMS
Sending outbound SMS (MT) is simply done through Jasmin’s HTTP API (refer to HTTP API for detailed information
about sending and receiving SMS and receipts):
http://127.0.0.1:1401/send?username=foo&password=bar&to=06222172&content=hello
Calling the above url from any brower will send an SMS to 06222172 with hello content, if you receive a response
like the below example it means your SMS is accepted for delivery:
Success "9ab2867c-96ce-4405-b890-8d35d52c8e01"
For more troubleshooting about message delivery, you can check details in related log files in /var/log/jasmin:
The RESTful API allows developers to expand and build their apps on Jasmin. The API makes it easy to send messages
to one or many destinations, check balance and routing, as well as enabling bulk messaging.
This API is built on the Falcon web framework and relying on a standard WSGI architecture, this makes it simple and
scalable.
If you need to use a stateful tcp protocol (SMPP v3.4), please refer to SMPP Server API.
SMS Messages can be transmitted using the RESTful api, the following requirements must be met to enable the
service:
• You need a Jasmin user account
• You need sufficient credit on your Jasmin user account
3.4.1 Installation
The RESTful API’s made available starting from v0.9rc16, it can be launched as a system service, so simply start it
by typing:
Note: The RESTful API works on Ubuntu16.04 and CentOS/RHEL 7.x out of the box, some requirements may be
installed manually if you are using older Ubuntu distributions.
If you are not using rpm/deb packages to install Jasmin then that systemd service may not be installed on your system,
you still can launch the RESTful API manually:
Configuration file for Celery and the Web server can be found in /etc/jasmin/rest-api.py.conf.
Note: You may also use any other WSGI server for better performance, eg: gunicorn with parallel workers . . .
Services
The Services resource represents all web services currently available via Jasmin’s RESTful API.
3.4.2 Authentication
Services having the /secure/ path (such as Send a single message and Route check) require authentication using Basic
Auth which transmits Jasmin account credentials as username/password pairs, encoded using base64.
Example:
We have passed the base64 encoded credentials through the Authorization header, ‘Zm9vOmJhcg==’ is the encoded
username:password pair (’foo:bar’), you can use any tool to base64 encode/decode.
If wrong or no authentication credentials are provided, a 401 Unauthorized error will be returned.
Note: Do not include username and password in the parameters, they are already provided through the Authorization
header.
Result Format:
If successful, response header HTTP status code will be 200 OK and and the message will be sent, the message id will
be returned in data.
Result Format:
If successful, response header HTTP status code will be 200 OK and and the messages will be sent, the batch id and
total message count will be returned in data.
Note: The Rest API server has an advanced QoS control to throttle pushing messages back to Jasmin, you may
fine-tune it through the http_throughput_per_worker and smart_qos parameters.
Sending binary messages can be done using single or batch messaging APIs.
It’s made possible by replacing the content parameter by the hex_content, the latter shall contain your binary data
hex value.
Example of sending a message with coding=8:
The hex_content used in the above example is the UTF16BE encoding of arabic word “”
(‘x06x23x06x31x06x46x06x28’).
Same goes for sending batches with binary data:
Usage examples:
The ref:parameter <restapi-POST_sendbatch_params> listed above can be used in many ways to setup a sendout
batch, we’re going to list some use cases to show the flexibility of these parameters:
Example 1, send different messages to different numbers::
{
"messages": [
{
"from": "Brand1",
"to": [
"55555551",
"55555552",
"55555553"
],
"content": "Message 1 goes to 3 numbers"
},
{
"from": "Brand2",
"to": [
"33333331",
"33333332",
"33333333"
],
"content": "Message 2 goes to 3 numbers"
},
{
"from": "Brand2",
"to": "7777771",
"content": "Message 3 goes to 1 number"
}
]
}
{
"globals" : {
"from": "Brand2",
"dlr-level": 3,
"dlr": "yes",
"dlr-url": "http://some.fancy/url"
}
"messages": [
{
"from": "Brand1",
"to": [
"55555551",
"55555552",
"55555553"
],
"content": "Message 1 goes to 3 numbers"
},
{
(continues on next page)
So, globals are vars to be inherited in messages, we still can force a local value in some messages like the “from”:
“Brand1” in the above example.
Example 3, using callbacks:
As explained, Jasmin is enqueuing a sendout batch everytime you call /secure/sendbatch, the batch job will run and
call Jasmin’s http api to deliver the messages, since this is running in background you can ask for success or/and error
callbacks to follow the batch progress.
{
"batch_config": {
"callback_url": "http://127.0.0.1:7877/successful_batch",
"errback_url": "http://127.0.0.1:7877/errored_batch"
},
"messages": [
{
"to": [
"55555551",
"55555552",
"55555553"
],
"content": "Hello world !"
},
{
"to": "7777771",
"content": "Holà !"
}
]
}
About callbacks:
The RESTful api is a wrapper around Jasmin’s http api, it relies on Celery task queue to process long running batches.
When you launch a batch, the api will enqueue the sendouts through Celery and return a batchId, that’s the Celery
task id.
Since the batch will be executed in background, the API provides a convenient way to follow its progression through
two different callbacks passed inside the batch parameters:
{
"batch_config": {
(continues on next page)
The callback_url will be called (GET) everytime a message is successfuly sent, otherwise the errback_url is called.
In both callbacks the following parameters are passed:
It is possible to schedule the launch of a batch, the api will enqueue the sendouts through Celery and return a batchId
while deferring message deliveries to the scheduled date & time.
{
"batch_config": {
"schedule_at": "2017-11-15 09:00:00"
},
"messages": [
{
"to": "7777771",
"content": "Good morning !"
}
]
}
The above batch will be scheduled for the 15th of November 2017 at 9am, the Rest API will consider it’s local server
time to make the delivery, so please make sure it’s accurate to whatever timezone you’re in.
It’s possible to use another schedule_at format:
{
"batch_config": {
"schedule_at": "86400s"
},
"messages": [
{
"to": "7777771",
(continues on next page)
The above batch will be scheduled for delivery in 1 day from now (86400 seconds = 1 day).
Note: Do not include username and password in the parameters, they are already provided through the Authorization
header.
Result Format:
If successful, response header HTTP status code will be 200 OK, the balance and the sms count will be returned in
data.
Note: Do not include username and password in the parameters, they are already provided through the Authorization
header.
Result Format:
If successful, response header HTTP status code will be 200 OK, the message rate and “pdu count” will be returned
in data.
3.4.8 Ping
A simple check to ensure this is a responsive Jasmin API, it is used by third party apps like Web campaigners, cluster
service checks, etc ..
Definition:
Examples:
Result Format:
{"data": "Jasmin/PONG"}
If successful, response header HTTP status code will be 200 OK and a static “Jasmin/PONG” value in data.
This document is targeted at software designers/programmers wishing to integrate SMS messaging as a function into
their applications using HTTP protocol, e.g. in connection with WEB-server, unified messaging, information services
etc..
If you need to use a stateful tcp protocol (SMPP v3.4), please refer to SMPP Server API.
SMS Messages can be transmitted using HTTP protocol, the following requirements must be met to enable the service:
• You need a Jasmin user account
• You need sufficient credit on your Jasmin user account1
3.5.1 Features
• Send and receive long (more than 160 characters) SMS, unicode/binary content and receive http callbacks when
a mobile station send you a SMS-MO.
• Check your balance status,
• Check a message rate price before sending it.
In order to deliver SMS-MT messages, Data is transferred using HTTP GET/POST requests. The Jasmin gateway
accepts requests at the following URL:
http://127.0.0.1:1401/send
Note: Host 127.0.0.1 and port 1401 are default values and configurable in /etc/jasmin/jasmin.cfg, see
jasmin.cfg / http-api.
This guide will help understand how the API works and provide Examples for sending SMS-MT.
When calling Jasmin’s URL from an application, the below parameters must be passed (at least mandatory ones), the
api will return a message id on success, see HTTP response.
HTTP response
When the request is validated, a SubmitSM PDU is set up with the provided request parameters and sent to the routed
connector through a AMQP queue, a queued message-id is returned:
Success "07033084-5cfd-4812-90a4-e4d24ffb6e3d"
Examples
And more use cases for sending long, UCS2 (UTF16) and binary messages:
# Python example
# http://jasminsms.com
import urllib.request, urllib.error, urllib.parse
import urllib.request, urllib.parse, urllib.error
˓→.....................................................'
urllib.request.urlopen("http://127.0.0.1:1401/send?%s" % urllib.parse.
˓→urlencode(baseParams)).read()
In PHP:
<?php
// Sending simple message using PHP
// http://jasminsms.com
$baseurl = 'http://127.0.0.1:1401/send'
$params = '?username=foo'
$params.= '&password=bar'
$params.= '&to='.urlencode('+336222172')
$params.= '&content='.urlencode('Hello world !')
$response = file_get_contents($baseurl.$params);
?>
In Ruby:
# Sending simple message using Ruby
# http://jasminsms.com
require 'net/http'
uri = URI('http://127.0.0.1:1401/send')
(continues on next page)
response = Net::HTTP.get_response(uri)
jasmin.cfg / http-api
The jasmin.cfg file (INI format, located in /etc/jasmin) contain a section called http-api where all ja-http API related
config elements are:
1 [http-api]
2 bind = 0.0.0.0
3 port = 1401
4
5 long_content_max_parts = 5
6 # Splitting long content can be made through SAR options or UDH
7 # Possible values are: sar and udh
8 long_content_split = udh
9
10 access_log = /var/log/jasmin/http-access.log
11 log_level = INFO
12 log_file = /var/log/jasmin/http-api.log
13 log_format = %(asctime)s %(levelname)-8s %(process)d %(message)s
14 log_date_format = %Y-%m-%d %H:%M:%S
When requested through dlr-* fields when Sending SMS-MT, a delivery receipt (DLR) will be sent back to the appli-
cation url (set in dlr-url) through HTTP GET/POST depending on dlr-method.
The receiving end point must reply back using a “200 OK” status header and a body containing an acknowledgement
of receiving the DLR, if one or both of these conditions are not met, the DLRThrower service will consider reshipment
of the same message if config/dlr-thrower/max_retries is not reached (see jasmin.cfg / dlr-thrower).
In order to acknowledge DLR receipt, the receiving end point must reply back with exactly the following html body
content:
ACK/Jasmin
Note: It is very important to acknowledge back each received DLR, this will prevent to receive the same message
many times, c.f. Processing for details
Note: Reshipment of a message will be delayed for config/dlr-thrower/retry_delay seconds (see jasmin.cfg / dlr-
thrower).
The following parameters are sent to the receiving end point (at dlr-url) when the DLR’s dlr-level is set to 1 (SMS-C
level only)
The following parameters are sent to the receiving end point (at dlr-url) when DLR’s dlr-level is set to 2 or 3 (Terminal
level or all levels)
Processing
The flowchart below describes how dlr delivery and retrying policy is done inside DLRThrower service:
jasmin.cfg / dlr-thrower
The jasmin.cfg file (INI format, located in /etc/jasmin) contain a section called deliversm-thrower where all DL-
RThrower service related config elements are:
1 [dlr-thrower]
2 http_timeout = 30
3 retry_delay = 30
4 max_retries = 3
5 log_level = INFO
(continues on next page)
SMS-MO incoming messages (Mobile Originated) are forwarded by Jasmin to defined URLs using simple HTTP
GET/POST, the forwarding is made by deliverSmHttpThrower service, and the URL of the receiving endpoint is
selected through a route checking process (c.f. The message router).
Receiving endpoint is a third party application which acts on the messages received and potentially generates replies,
(HTTP Client connector manager for more details about HTTP Client connector management).
The parameters below are transmitted for each SMS-MO, the receiving end point must provide an url (set in jas-
minApi.HttpConnector.baseurl) and parse the below parameters using GET or POST method (depends on jasmi-
nApi.HttpConnector.method).
The receiving end point must reply back using a “200 OK” status header and a body containing an acknowledgement
of receiving the SMS-MO, if one or both of these conditions are not met, the deliverSmHttpThrower service will
consider reshipment of the same message if config/deliversm-thrower/max_retries is not reached, (see jasmin.cfg /
deliversm-thrower).
In order to acknowledge SMS-MO receipt, the receiving end point must reply back with exactly the following html
body content:
ACK/Jasmin
Note: It is very important to acknowledge back each received SMS-MO, this will prevent to receive the same message
many times, c.f. Processing for details
Note: Reshipment of a message will be delayed for config/deliversm-thrower/retry_delay seconds (see jasmin.cfg
/ deliversm-thrower).
HTTP Parameters
When receiving an URL call from Jasmin’s deliverSmHttpThrower service, the below parameters are delivered (at
least Always present ones).
Note: When receiving multiple parts of a long SMS-MO, deliverSmHttpThrower service will concatenate the content
of all the parts and then throw one http call with concatenated content.
Processing
The flowchart below describes how message delivery and retrying policy are done inside deliverSmHttpThrower ser-
vice:
jasmin.cfg / deliversm-thrower
The jasmin.cfg file (INI format, located in /etc/jasmin) contain a section called deliversm-thrower where all deliv-
erSmHttpThrower service related config elements are:
1 [deliversm-thrower]
2 http_timeout = 30
3 retry_delay = 30
4 max_retries = 3
5 log_level = INFO
6 log_file = /var/log/jasmin/deliversm-thrower.log
(continues on next page)
2
In order to check user account balance and quotas, user may request a HTTP GET/POST from the following URL:
http://127.0.0.1:1401/balance
Note: Host 127.0.0.1 and port 1401 are default values and configurable in /etc/jasmin/jasmin.cfg, see
jasmin.cfg / http-api.
HTTP response
Successful response:
{"balance": 100.0, "sms_count": "ND"}
Examples
print('Balance:', response['balance'])
print('SMS Count:', response['sms_count'])
#Balance: 100.0
#SMS Count: ND
It is possible to ask Jasmin’s HTTPAPI for a message rate price before sending it, the request will lookup the route to
be considered for the message and will provide the rate price if defined.
Request is done through HTTP GET/POST to the following URL:
http://127.0.0.1:1401/rate
Note: Host 127.0.0.1 and port 1401 are default values and configurable in /etc/jasmin/jasmin.cfg, see
jasmin.cfg / http-api.
HTTP response
Successful response:
Where submit_sm_count is the number of message units if the content is longer than 160 characters, content param-
eter is optional for requesting rate price.
Otherwise, an error is returned.
Otherwise, an error is returned:
Examples
# Python example
# http://jasminsms.com
import urllib.request, urllib.error, urllib.parse
import urllib.request, urllib.parse, urllib.error
import json
response = json.loads(response)
This document is targeted at software designers/programmers wishing to integrate SMS messaging through a stateful
tcp protocol SMPP v3.4, if you feel this does not fit your needs and that you are more “web-service-guy” then you
still can try HTTP API.
SMS Messages can be transmitted using SMPP protocol, the following requirements must be met to enable the service
:
• You need a Jasmin user account
• You need sufficient credit on your Jasmin user account1
3.6.1 Features
The SMPP Server API allows you to send and receive SMS and delivery receipts (DLR) through Jasmin’s connectors,
send and receive long (more than 160 characters) SMS and unicode/binary content.
jasmin.cfg / smpp-server
The jasmin.cfg file (INI format, located in /etc/jasmin) contain a section called smpp-server where all SMPP Server
API related config elements are:
1 [smpp-server]
2 id = "smpps_01"
3 bind = 0.0.0.0
4 port = 2775
5
6 sessionInitTimerSecs = 30
7 enquireLinkTimerSecs = 30
8 inactivityTimerSecs = 300
9 responseTimerSecs = 60
10 pduReadTimerSecs = 30
11
12 log_level = INFO
13 log_file = /var/log/jasmin/default-smpps_01.log
14 log_format = %(asctime)s %(levelname)-8s %(process)d %(message)s
15 log_date_format = %Y-%m-%d %H:%M:%S
Using a proper SMPP Client application (or a Jasmin SMPP Client), the following parameters must be considered:
The message router is Jasmin’s decision making component for routing every type of exchanged message through the
gateway:
1. MO Messages (deliver_sm)
2. MT Messages (submit_sm)
The router is provisioned through:
• Perspective broker interface (python programmatic API)
• jCli modules: MO router manager and MT router manager
Each time a message is requiring a route decision the following process is executed:
There’s one MORoutingTable and one MTRoutingTable objects holding respective routes for each direction (MT
or MO), these are Route objects that hold one or many Filter (s) objects and one destination Connector (or many
connectors in some specific cases, c.f. Multiple connectors).
As explained by the above routing process flow figure, for each message and depending on its direction, a routing
table is loaded and an iterative testing is run in order to select a final destination connector or to reject (returning no
connector) it, routes are selected in descendant order, and their respective filter objects are tested against the Routable
object (It is an extension of the low-level SMPP PDU object representing a message, more information in Routable).
Examples
MO Routing
Having the below MO routing table set through a jCli console session:
jcli : morouter -l
#MO Route order Type Connector ID(s) Filter(s)
#30 StaticMORoute http_3
˓→<DestinationAddrFilter (dst_addr=^\+33\d+)>
˓→18:00:00)>
#0 DefaultRoute http_def
Total MO Routes: 3
MT Routing
Having the below MT routing table set through a jCli console session:
jcli : mtrouter -l
#MT Route order Type Rate Connector ID(s)
˓→Filter(s)
Total MT Routes: 3
Note: The route order is very important: if we swap last both routes (#90 and #91) we will run into a shadowing route
where all MT messages sent by a user in group G2 will be routed to smpp_3, no matter what time of the day it is.
Note: In this example, there’s no DefaultRoute, this will lead to message rejection if none of the configured routes
are matched.
The router components are mainly python objects having the unique responsibility of routing messages to Jasmin
connectors.
Routable
The Routable class is extended by child classes to hold necessary information about the message to be routed.
Fig. 3: jasmin.routing.Routables.*
The SimpleRoutablePDU is only used for Jasmin unit testing, RoutableSubmitSm and RoutableDeliverSm are
used depending on the message direction:
• MO: RoutableDeliverSm
• MT: RoutableSubmitSm
All routables provide a tagging api through the addTag(), hasTag(), getTags(), removeTag(), flushTags() methods, this
feature is mainly used in the interceptor, there’s a concrete example of such usage here.
Connector
The Connector class is extended by child classes to represent concrete HTTP or SMPP Client connectors.
Filter
The Filter class is extended by child classes to define specific filters which are run by Jasmin router to match a desired
Routable, every filter have a public match(routable) method returning a boolean value (True if the filter matches the
given Routable).
As explained, filters provide an advanced and customizable method to match for routables and decide which route to
consider, the figure below shows the Filter implementations provided by Jasmin, you can extend the Filter class and
build a new filter of your own.
The usedFor attribute indicates the filter-route compatibility, as some filters are not suitable for both MO and MT
routes like the examples below:
• UserFilter and GroupFilter: MO Messages are not identified by a user or a group, they are received through a
connector
• ConnectorFilter: MT Messages are not coming from a connector, they are sent by a known user/group.
Fig. 5: jasmin.routing.Filters.*
Route
A Route class holds one or many filters, the matchFilters(routable) method is called to match the given routable
against every filter of the Route (using AND operation when there’s many filters), if the matching succeed, the Jamsin
router will ask for the Connector to consider by calling getConnector() method which will return back the Route ‘s
connector.
Static and default routes are the simplest implemented routes, the difference between them is:
• DefaultRoute ‘s matchFilter() method will always return True, it is usually a fallback route matching any
Routable
• StaticMORoute and StaticMTRoute will return one Connector after matching the filters with matchFil-
ters(routable) method
There’s a lot of things you can do by extending the Route class, here’s a bunch of possibilities:
• Best quality routing: Implement a connector scoring system to always return the best quality route for a given
message
Multiple connectors
When extending Route class, it is possible to customize the behavior of the route and that’s what Roundrobin-
MORoute and RoundrobinMTRoute do, they are initially provisioned with a set of connectors, and the getCon-
nector() method is overloaded to return a random connector from it; this can be a basic usage of a load balancer
route.
Fig. 6: jasmin.routing.Routes.*
The newly added (Jasmin 0.9b10+) has new FailoverMORoute and FailoverMTRoute routes, they are also extending
the Route class to provide failover on top of multiple connectors.
RoutingTable
The RoutingTable class is extended by destination-specific child classes (MO or MT), each class provide a Route
provisioning api:
• add(route, order): Will add a new route at a given order, will replace an older route having the same order
• remove(order): Will remove the route at the given order
• getAll(): Will return all the provisioned routes
• flush(): Will remove all provisioned routes
The getRouteFor(routable) will get the right route to consider for a given routable, this method will iterate through
all the provisioned routes in descendant order to call their respective matchFilters(routable) method.
Fig. 7: jasmin.routing.RoutingTables.*
3.8 Interception
Starting from 0.7.0, Jasmin provides a convenient way for users to hook third party logics on intercepted messages
(submit_sm or deliver_sm) before proceding to The message router.
Interception of message is based on filter matching, just like the router; every intercepted message will be handed to a
user-script written in Python.
This feature permits users to implement custom behaviors on top of Jasmin router, here’s some possible scenarios:
• Billing & charging of MO messages,
Jasmin’s interceptor is a system service that run separately from Jasmin, it can be hosted on remote server as well;
interceptord is a system service just like jasmind, so simply start it by typing:
Note: After starting the interceptord service, you may check /var/log/jasmin/interceptor.log to ensure everything is
okay.
Then you need to enable communication between jasmind and interceptord services by editing jasmind start script
(locate the jasmind.service file in /etc/systemd) and replacing the following line:
by:
The last step is to restart jasmind and check /var/log/jasmin/interceptor.log to ensure connection has been successfully
established by finding the following line:
As stated earlier, interceptor is behaving similarly to The message router, here’s an example of setting up a MO
message (deliver_sm) interception rule through jcli management console:
jcli : mointerceptor -a
Adding a new MO Interceptor: (ok: save, ko: exit)
> type DefaultInterceptor
<class 'jasmin.routing.Interceptors.DefaultInterceptor'> arguments:
script
> script python3(/opt/jasmin-scripts/interception/mo-interceptor.py)
> ok
Successfully added MOInterceptor [DefaultInterceptor] with order:0
Same thing apply to setting up a MT message (submit_sm) interception rule, here’s another example using a filtered
rule instead of a default one:
3.8. Interception 47
Jasmin Documentation, Release 0.10.11s
jcli : mtinterceptor -a
Adding a new MT Interceptor: (ok: save, ko: exit)
> type StaticMTInterceptor
<class 'jasmin.routing.Interceptors.DefaultInterceptor'> arguments:
filters, script
> script python3(/opt/jasmin-scripts/interception/mt-interceptor.py)
> filters U-foo;DA-33
> order 100
> ok
Successfully added MTInterceptor [StaticMTInterceptor] with order:100
As show in the above examples, the interception rules are straightforward, any matched message will be handed to the
script you set through the script python3(<path_to_pyfile>) instruction.
When your python script is called it will get the following global variables set:
• routable: one of the jasmin.routing.Routables.Routable inheriters (Routable for more details)
• smpp_status: (default to 0) it is the smpp response that Jasmin must return for the message, more details in
Controlling response
• http_status: (default to 0) it is the http response that Jasmin must return for the message, more details in
Controlling response
The script can:
• Override routable parameters like setting destination or source addresses, short message, etc . . .
• Tag the routable to help the router matching a desired rule (useful for HRL lookup routing)
• Control Jasmin response by setting smpp_status and/or http_status.
Some practical examples are given below.
The interceptor script can reject message before it goes to the router, this can be useful for implementing third party
controls like:
• Billing and charging authorization: reject message if user has no credits,
• Reject some illegal message content,
• Enable anti-spam to protect destination users from getting flooded,
• etc . . .
In order to reject a message, depending on the source of message (httpapi ? smpp server ? smpp client ?) the script
must set smpp_status and/or http_status accordingly to the error to be returned back, here’s an error mapping table
for smpp:
3.8. Interception 49
Jasmin Documentation, Release 0.10.11s
As for http errors, the value you set in http_status will be the http error code to return.
Note: When setting http_status to some value different from 0, the smpp_status value will be automatically set to
255 (ESME_RUNKNOWNERR).
Note: When setting smpp_status to some value different from 0, the http_status value will be automatically set to
520 (Unknown error).
Note: When setting smpp_status to 0, the routing process will be bypassed and an ESME_ROK status is returned.
You’ll find below some helping examples of scripts used to intercept MO and/or MT messages.
The following script will help the router decide where to send the MT message, let’s say we have some HLR lookup
webservice to call in order to know to which network the destination number belong, and then tag the routable for later
filtering in router:
"This script will call HLR lookup api to get the MCC/MNC of the destination number"
hlr_lookup_url = "https://api.some-provider.com/hlr/lookup"
data = json.dumps({'number': routable.pdu.params['destination_addr']})
r = requests.post(hlr_lookup_url, data, auth=('user', '*****'))
The script is tagging the routable if destination is Vodaphone, Orange or Lyca mobile; that’s because we need to route
message to different connector based on destination network, let’s say:
• Vodaphone needs to be routed through connectorA
• Orange needs to be routed through connectorB
• Lyca mobile needs to be routed through connectorC
• All the rest needs to be routed through connectorD
Here’s the routing table to execute the above example:
jcli : mtrouter -l
#Order Type Rate Connector ID(s) Filter(s)
#102 StaticMTRoute 0 (!) smppc(connectorA) <TG (tag=21401)>
#101 StaticMTRoute 0 (!) smppc(connectorB) <TG (tag=21403)>
#100 StaticMTRoute 0 (!) smppc(connectorC) <TG (tag=21425)>
#0 DefaultRoute 0 (!) smppc(connectorD)
Total MT Routes: 4
MO Charging
In this case, the script is calling CGRateS charging system to check if user has sufficient balance to send sms, based
on the following script, Jasmin will return a ESME_ROK if user balance, or ESME_RDELIVERYFAILURE if not:
"""This script will receive Mobile-Originated messages and
ask CGRateS for authorization through ApierV2.GetMaxUsage call.
"""
import json, socket
from datetime import datetime
CGR_HOST="172.20.20.140"
CGR_PORT=3422
3.8. Interception 51
Jasmin Documentation, Release 0.10.11s
return response.get('result')
sck = None
globals()['sck'] = sck
globals()['json'] = json
try:
sck = socket.create_connection((CGR_HOST, CGR_PORT))
There’s some cases where you need to override sender-id due to some MNO policies, in the following example all
intercepted messages will have their sender-id set to 123456789:
routable.pdu.params['source_addr'] = '123456789'
Note: Some pdu parameters require locking to protect them from being updated by Jasmin, more on this.
In order to change the ton or npi value for source or destination address, the according values need to be set and locked,
in order to prevent them from getting overwritten by the client connector:
routable.pdu.params['source_addr_ton'] = AddrTon.ALPHANUMERIC;;
routable.lockPduParam('source_addr_ton');
routable.pdu.params['source_addr_npi'] = AddrNpi.ISDN;
routable.lockPduParam('source_addr_npi');
routable.pdu.params['dest_addr_ton'] = AddrTon.INTERNATIONAL;
routable.lockPduParam('dest_addr_ton');
routable.pdu.params['dest_addr_npi'] = AddrNpi.ISDN;
routable.lockPduParam('dest_addr_npi');
Activate logging
import logging
# Set logger
logger = logging.getLogger('logging-example')
if len(logger.handlers) != 1:
hdlr = logging.FileHandler('/var/log/jasmin/some_file.log')
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
logger.setLevel(logging.DEBUG)
Enforcing DLR
Ask for DLR for all submit_sm pdus, no matter the downstream user choice, can be used for route qualification and
scoring purposes.
3.8. Interception 53
Jasmin Documentation, Release 0.10.11s
"This script will enforce sending message while asking for DLR"
routable.pdu.params['registered_delivery'] = RegisteredDelivery(
RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED)
Subsequent chapters present how to send and receive messages through Jasmin HTTP API and some more advanced
use cases, such as manipulating receipts and complex routings, will look like.
It is assumed the reader has already installed Jasmin and at least read the HTTP API and The message router chapters
and knows enough about Jasmin’s architecture/design concepts.
# Python example
# http://jasminsms.com
import urllib.request, urllib.error, urllib.parse
import urllib.request, urllib.parse, urllib.error
In PHP:
<?php
// Sending simple message using PHP
// http://jasminsms.com
$baseurl = 'http://127.0.0.1:1401/send'
$params = '?username=foo'
$params.= '&password=bar'
$params.= '&to='.urlencode('+336222172')
$params.= '&content='.urlencode('Hello world !')
$response = file_get_contents($baseurl.$params);
?>
In Ruby:
require 'net/http'
uri = URI('http://127.0.0.1:1401/send')
params = { :username => 'foo', :password => 'bar',
:to => '+336222172', :content => 'Hello world' }
uri.query = URI.encode_www_form(params)
response = Net::HTTP.get_response(uri)
c.f. HTTP API for more details about sending SMS with receipt enquiry, long content etc . . .
Receiving a SMS is done through the HTTP API, this a PHP script pointed by Jasmin for every received SMS (using
routing):
<?php
// Receiving simple message using PHP through HTTP Post
// This example will store every received SMS to a SQL table
// http://jasminsms.com
$MO_SMS = $_POST;
if (!$db)
// We'll not ACK the message, Jasmin will resend it later
die("Error connecting to DB");
$QUERY = "INSERT INTO sms_mo(id, from, to, cid, priority, coding, validity, content)
˓→";
$QUERY.= "VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s');";
$Q = sprintf($QUERY, pg_escape_string($MO_SMS['id']),
pg_escape_string($MO_SMS['from']),
pg_escape_string($MO_SMS['to']),
pg_escape_string($MO_SMS['origin-connector']),
pg_escape_string($MO_SMS['priority']),
pg_escape_string($MO_SMS['coding']),
pg_escape_string($MO_SMS['validity']),
pg_escape_string($MO_SMS['content'])
);
pg_query($Q);
pg_close($db);
In the above example, there’s an error handling where the message is not ACKed if there’s a database connection
problem, if it occurs, the script will return “Error connecting to DB” when Jasmin HTTP thrower is waiting for a
“ACL/Jasmin”, this will lead to a message re-queue and later re-delivery to the same script, this behaviour is explained
in Processing.
<?php
// Will filter received messages, if the syntax is correct (weather <city name>)
// it will provide a `fake` weather forecast back to the user.
// http://jasminsms.com
$MO_SMS = $_POST;
// Syntax check
if (!preg_match('/^(weather) (.*)/', $MO_SMS['content'], $matches))
$RESPONSE = "SMS Syntax error, please type 'weather city' to get a fresh weather
˓→forecast";
else
$RESPONSE = $martches[2]." forecast: Sunny 21°C, 13Knots NW light wind";
$response = file_get_contents($baseurl.$params);
// Note:
// If you need to check if the message is really delivered (or at least, taken by
˓→Jasmin for delivery)
// you must test for $response value, it must begin with "Success", c.f. HTTP API doc
˓→for more details
3.9.3 Routing
c.f. MO router manager and MT router manager for routing scenarios. c.f. The message router for details about
routing.
jCli is Jasmin’s CLI interface, it is an advanced console to manage and configure everything needed to start messaging
through Jasmin, from users to connectors and message routing management.
jCli is multi-profile configurator where it is possible to create a testing, staging and production profiles to hold different
sets of configurations depending on the desired execution environment.
In order to connect to jCli and start managing Jasmin, the following requirements must be met:
• You need a jCli admin account
• You need to have a connection to jCli’s tcp port
Jasmin management through jCli is done using different modules (users, groups, filters, smpp connectors, http con-
nectors . . . ), these are detailed in Management CLI Modules, before going to this part, you have to understand how
to:
• Configure jCli to change it’s binding host and port, authentication and logging parameters,
• Authenticate to jCli and discover basic commands to navigate through the console,
• Know how to persist to disk the current configuration before restarting or load a specific configuration profile to
run test scenarios for example
3.10.1 Architecture
The Jasmin CLI interface is designed to be a user interactive interface on front of the Perspective brokers provided by
Jasmin.
In the above figure, every Jasmin CLI module (blue boxes) is connected to its perspective broker, and below you find
more details on the Perspective brokers used and the actions they are exposing:
• SMPPClientManagerPB which provides the following actions:
1. persist: Persist current configuration to disk
2. load: Load configuration from disk
3. is_persisted: Used to check if the current configuration is persisted or not
4. connector_add: Add a SMPP Client connector
5. connector_remove: Remove a SMPP Client connector
6. connector_list: List all SMPP Client connectors
7. connector_start: Start a SMPP Client connector
8. connector_stop: Stop a SMPP Client connector
9. connector_stopall: Stop all SMPP Client connectors
10. service_status: Return a SMPP Client connector service status (running or not)
11. session_state: Return a SMPP Client connector session state (SMPP binding status)
12. connector_details: Get all details for a gived SMPP Client connector
13. connector_config: Returns a SMPP Client connector configuration
14. submit_sm: Send a submit_sm *
• RouterPB which provides the following actions:
1. persist: Persist current configuration to disk
2. load: Load configuration from disk
3. is_persisted: Used to check if the current configuration is persisted or not
4. user_add: Add a new user
5. user_authenticate: Authenticate username/password with the existent users *
6. user_remove: Remove a user
7. user_remove_all: Remove all users
8. user_get_all: Get all users
9. user_update_quota: Update a user quota
10. group_add: Add a group
11. group_remove: Remove a group
12. group_remove_all: Remove all groups
13. group_get_all: Get all groups
14. mtroute_add: Add a new MT route
15. moroute_add: Add a new MO route
16. mtroute_remove: Remove a MT route
17. moroute_remove: Remove a MO route
18. mtroute_flush: Flush MT routes
19. moroute_flush: Flush MO routes
20. mtroute_get_all: Get all MT routes
21. moroute_get_all: Get all MO routes
22. mtinterceptor_add: Add a new MT interceptor
23. mointerceptor_add: Add a new MO interceptor
24. mtinterceptor_remove: Remove a MT interceptor
25. mointerceptor_remove: Remove a MO interceptor
26. mtinterceptor_flush: Flush MT interceptor
27. mointerceptor_flush: Flush MO interceptor
28. mtinterceptor_get_all: Get all MT interceptor
29. mointerceptor_get_all: Get all MO interceptor
Hint: SMPPClientManagerPB and RouterPB are available for third party applications to implement specific busi-
ness processes, there’s a FAQ subject including an example of how an external application can use these Perspective
Brokers.
3.10.2 Configuration
The jasmin.cfg file (INI format, located in /etc/jasmin) contains a jcli section where all JCli interface related config
elements are:
1 [jcli]
2 bind = 127.0.0.1
3 port = 8990
4 authentication = True
5 admin_username = jcliadmin
6 # MD5 password digest hex encoded
7 admin_password = 79e9b0aa3f3e7c53e916f7ac47439bcb
8
9 log_level = INFO
10 log_file = /var/log/jasmin/jcli.log
11 log_format = %(asctime)s %(levelname)-8s %(process)d %(message)s
12 log_date_format = %Y-%m-%d %H:%M:%S
Warning: Don’t set authentication to False if you’re not sure about what you are doing
In order to connect to jCli, initiate a telnet session with the hostname/ip and port of jCli as set in Configuration:
And depending on whether authentication is set to True or False, you may have to authenticate using the ad-
min_username and admin_password, here’s an example of an authenticated connection:
Authentication required.
Username: jcliadmin
Password:
Welcome to Jasmin console
Type help or ? to list commands.
Session ref: 2
jcli :
Once successfully connected, you’ll get a welcome message, your session id (Session ref) and a prompt (jcli : ) where
you can start typing your commands and use Management CLI Modules.
Available commands:
jcli : [TABULATION]
persist load user group filter mointerceptor mtinterceptor morouter mtrouter smppccm
˓→httpccm quit help
Or type help and you’ll get detailed listing of the available commands with comprehensive descriptions:
jcli : help
Available commands:
===================
persist Persist current configuration profile to disk in PROFILE
load Load configuration PROFILE profile from disk
user User management
group Group management
filter Filter management
mointerceptor MO Interceptor management
mtinterceptor MT Interceptor management
morouter MO Router management
mtrouter MT Router management
smppccm SMPP connector management
httpccm HTTP client connector management
Control commands:
=================
quit Disconnect from console
help List available commands with "help" or detailed help with "help
˓→cmd".
More detailed help for a specific command can be obtained running help cmd where cmd is the command you need
help for:
Options:
-l, --list List all users or a group users when provided with GID
-a, --add Add user
-u UID, --update=UID Update user using it's UID
(continues on next page)
Interactivity:
When running a command you may enter an interactive session, for example, adding a user with user -a will start
an interactive session where you have to indicate the user parameters, the prompt will be changed from jcli : to >
indicating you are in an interactive session:
jcli : user -a
Adding a new User: (ok: save, ko: exit)
> username foo
> password bar
> uid u1
> gid g1
> ok
Successfully added User [u1] to Group [g1]
In the above example, user parameters were username, password, uid and gid, note that there’s no order in entering
these parameters, and you may use a simple TABULATION to get the parameters you have to enter:
...
> [TABULATION]
username password gid uid
...
Everything done using the Jasmin console will be set in runtime memory, and it will remain there until Jasmin is
stopped, that’s where persistence is needed to keep the same configuration when restarting.
Persist
Typing persist command below will persist runtime configuration to disk using the default profile set in Configuration:
jcli : persist
mtrouter configuration persisted (profile:jcli-prod)
smppcc configuration persisted (profile:jcli-prod)
group configuration persisted (profile:jcli-prod)
user configuration persisted (profile:jcli-prod)
httpcc configuration persisted (profile:jcli-prod)
mointerceptor configuration persisted (profile:jcli-prod)
filter configuration persisted (profile:jcli-prod)
mtinterceptor configuration persisted (profile:jcli-prod)
morouter configuration persisted (profile:jcli-prod)
Important: On Jasmin startup, jcli-prod profile is automatically loaded, any other profile can only be manually
loaded through load -p AnyProfile.
Load
Like persist command, there’s a load command which will loaded a configuration profile from disk, typing load
command below will load the default profil set in Configuration from disk:
jcli : load
mtrouter configuration loaded (profile:jcli-prod)
smppcc configuration loaded (profile:jcli-prod)
group configuration loaded (profile:jcli-prod)
user configuration loaded (profile:jcli-prod)
httpcc configuration loaded (profile:jcli-prod)
mointerceptor configuration loaded (profile:jcli-prod)
filter configuration loaded (profile:jcli-prod)
mtinterceptor configuration loaded (profile:jcli-prod)
morouter configuration loaded (profile:jcli-prod)
Note: When loading a profile, any defined current runtime configuration will lost and replaced by this profile config-
uration
As shown in the architecture figure Architecture, jCli is mainly composed of management modules interfacing two
Perspective brokers (SMPPClientManagerPB and RouterPB), each module is identified as a manager of a defined
scope:
• User management
• Group management
• etc ..
Note: filter and httpccm modules are not interfacing any Perspective broker, they are facilitating the reuse of created
filters and HTTP Client connectors in MO and MT routers, e.g. a HTTP Client connector may be created once and
used many times in MO Routes.
The User manager module is accessible through the user command and is providing the following features:
jcli : user -a
Adding a new User: (ok: save, ko: exit)
> username foo
> password bar
> gid marketing
> uid foo
> ok
Successfully added User [foo] to Group [marketing]
All the above parameters can be displayed after User creation, except the password:
Listing Users will show currently added Users with their UID, GID and Username:
jcli : user -l
#User id Group id Username Balance MT SMS Throughput
#foo 1 foo ND ND ND/ND
Total Users: 1
Note: When listing a disabled user, his User id will be prefixed by !, same thing apply to group.
User credentials
MT Messaging section
As seen above, User have an optional mt_messaging_cred parameter which define a set of sections:
• Authorizations: Privileges to send messages and set some defined parameters,
• Value filters: Restrictions on some parameter values (such as source address),
• Default values: Default parameter values to be set by Jasmin when not manually set by User,
• Quotas: Everything about Billing,
For each section of the above, there’s keys to be defined when adding/updating a user, the example below show how
to set a source address value filter, a balance of 44.2, unlimited sms_count and limit SMS throughput in smpp server
to 2 messages per second:
jcli : user -a
Adding a new User: (ok: save, ko: exit)
> username foo
> password bar
> gid marketing
> uid foo
> mt_messaging_cred valuefilter src_addr ^JASMIN$
> mt_messaging_cred quota balance 44.2
> mt_messaging_cred quota sms_count none
> mt_messaging_cred quota smpps_throughput 2
> ok
Successfully added User [foo] to Group [marketing]
Note: Setting none value to a user quota will set it as unlimited quota.
In the below tables, you can find exhaustive list of keys for each mt_messaging_cred section:
Note: Authorizations keys prefixed by http_ or smpps_ are only applicable for their respective channels.
Note: It is possible to increment a quota by indicating a sign, ex: +10 will increment a quota value by 10, -22.4 will
decrease a quota value by 22.4.
User have an other optional smpps_cred parameter which define a specialized set of sections for defining his creden-
tials for using the SMPP Server API:
• Authorizations: Privileges to bind,
• Quotas: Maximum bound connections at a time (multi binding),
For each section of the above, there’s keys to be defined when adding/updating a user, the example below show how
to authorize binding and set max_bindings to 2:
jcli : user -a
Adding a new User: (ok: save, ko: exit)
> username foo
> password bar
> gid marketing
> uid foo
> smpps_cred authorization bind yes
> smpps_cred quota max_bindings 2
> ok
Successfully added User [foo] to Group [marketing]
In the below tables, you can find exhaustive list of keys for each smpps_cred section:
Note: It is possible to increment a quota by indicating a sign, ex: +10 will increment a quota value by 10, -2 will
decrease a quota value by 2.
The Group manager module is accessible through the group command and is providing the following features:
jcli : group -a
Adding a new Group: (ok: save, ko: exit)
> gid marketing
> ok
Successfully added Group [marketing]
Listing Groups will show currently added Groups with their GID:
jcli : group -l
#Group id
#marketing
Total Groups: 1
The MO Router manager module is accessible through the morouter command and is providing the following features:
Note: MO Route is used to route inbound messages (SMS MO) through two possible channels: http and smpps
(SMPP Server).
MO Router helps managing Jasmin’s MORoutingTable, which is responsible of providing routes to received SMS
MO, here are the basics of Jasmin MO routing mechanism:
1. MORoutingTable holds ordered MORoute objects (each MORoute has a unique order)
2. A MORoute is composed of:
• Filters: One or many filters (c.f. Filter manager)
• Connector: One connector (can be many in some situations)
3. There’s many objects inheriting MORoute to provide flexible ways to route messages:
• DefaultRoute: A route without a filter, this one can only set with the lowest order to be a default/fallback
route
• StaticMORoute: A basic route with Filters and one Connector
• RandomRoundrobinMORoute: A route with Filters and many Connectors, will return a random Con-
nector if its Filters are matched, can be used as a load balancer route
• FailoverMORoute: A route with Filters and many Connectors, will return an available (connected)
Connector if its Filters are matched
4. When a SMS MO is received, Jasmin will ask for the right MORoute to consider, all routes are checked in
descendant order for their respective Filters (when a MORoute have many filters, they are checked with an
AND boolean operator)
5. When a MORoute is considered (its Filters are matching a received SMS MO), Jasmin will use its Connector
to send the SMS MO.
Check The message router for more details about Jasmin’s routing.
When adding a MO Route, the following parameters are required:
• type: One of the supported MO Routes: DefaultRoute, StaticMORoute, RandomRoundrobinMORoute
• order: MO Route order
When choosing the MO Route type, additional parameters may be added to the above required parameters.
Here’s an example of adding a DefaultRoute to a HTTP Client Connector (http_default):
jcli : morouter -a
Adding a new MO Route: (ok: save, ko: exit)
> type DefaultRoute
jasmin.routing.Routes.DefaultRoute arguments:
connector
> connector http(http_default)
> ok
Successfully added MORoute [DefaultRoute] with order:0
Note: You don’t have to set order parameter when the MO Route type is DefaultRoute, it will be automatically set
to 0
jcli : morouter -a
Adding a new MO Route: (ok: save, ko: exit)
> type StaticMORoute
jasmin.routing.Routes.StaticMORoute arguments:
filters, connector
> order 10
> filters filter_1
> connector http(http_1)
> ok
Successfully added MORoute [StaticMORoute] with order:10
jcli : morouter -a
Adding a new MO Route: (ok: save, ko: exit)
> type StaticMORoute
jasmin.routing.Routes.StaticMORoute arguments:
filters, connector
> order 15
> filters filter_2
> connector smpps(user_1)
> ok
Successfully added MORoute [StaticMORoute] with order:15
Note: When routing to a smpps connector like the above example the user_1 designates the username of the
concerned user, if he’s already bound to Jasmin’s SMPP Server API routed messages will be delivered to him, if not,
queuing will take care of delivery.
Here’s an example of adding a RandomRoundrobinMORoute to two HTTP Client Connectors (http_2 and http_3):
jcli : morouter -a
Adding a new MO Route: (ok: save, ko: exit)
> type RandomRoundrobinMORoute
jasmin.routing.Routes.RandomRoundrobinMORoute arguments:
filters, connectors
> filters filter_3;filter_1
> connectors http(http_2);http(http_3)
> order 20
> ok
Successfully added MORoute [RandomRoundrobinMORoute] with order:20
Here’s an example of adding a FailoverMORoute to two HTTP Client Connectors (http_4 and http_5):
jcli : morouter -a
Adding a new MO Route: (ok: save, ko: exit)
> type FailoverMORoute
jasmin.routing.Routes.FailoverMORoute arguments:
filters, connectors
> filters filter_4
> connectors http(http_4);http(http_5)
> order 30
> ok
Successfully added MORoute [FailoverMORoute] with order:20
Note: It is not possible to use a FailoverMORoute with a mix of connectors, example: connectors
smpps(user_1);http(http_1);http(http_3).
Once the above MO Routes are added to MORoutingTable, it is possible to list these routes:
jcli : morouter -l
#Order Type Connector ID(s) Filter(s)
#30 FailoverMORoute http(http_4), http(http_5) <T>, <T>
#20 RandomRoundrobinMORoute http(http_2), http(http_3) <T>, <T>
#15 StaticMORoute smpps(user_1) <T>
#10 StaticMORoute http(http_1) <T>
#0 DefaultRoute http(http_default)
Total MO Routes: 3
Note: Filters and Connectors were created before creating these routes, please check Filter manager and HTTP Client
connector manager for further details
jcli : morouter -s 20
RandomRoundrobinMORoute to 2 connectors:
- http(http_2)
- http(http_3)
jcli : morouter -s 10
StaticMORoute to http(http_1)
jcli : morouter -s 0
DefaultRoute to http(http_default)
The MT Router manager module is accessible through the mtrouter command and is providing the following features:
Note: MT Route is used to route outbound messages (SMS MT) through one channel: smppc (SMPP Client).
MT Router helps managing Jasmin’s MTRoutingTable, which is responsible of providing routes to outgoing SMS MT,
here are the basics of Jasmin MT routing mechanism:
1. MTRoutingTable holds ordered MTRoute objects (each MTRoute has a unique order)
2. A MTRoute is composed of:
• Filters: One or many filters (c.f. Filter manager)
• Connector: One connector (can be many in some situations)
• Rate: For billing purpose, the rate of sending one message through this route; it can be zero to mark the
route as FREE (NOT RATED) (c.f. Billing)
3. There’s many objects inheriting MTRoute to provide flexible ways to route messages:
• DefaultRoute: A route without a filter, this one can only set with the lowest order to be a default/fallback
route
• StaticMTRoute: A basic route with Filters and one Connector
• RandomRoundrobinMTRoute: A route with Filters and many Connectors, will return a random Con-
nector if its Filters are matching, can be used as a load balancer route
• FailoverMTRoute: A route with Filters and many Connectors, will return an available (connected)
Connector if its Filters are matched
4. When a SMS MT is to be sent, Jasmin will ask for the right MTRoute to consider, all routes are checked in
descendant order for their respective Filters (when a MTRoute have many filters, they are checked with an
AND boolean operator)
5. When a MTRoute is considered (its Filters are matching an outgoing SMS MT), Jasmin will use its Connector
to send the SMS MT.
Check The message router for more details about Jasmin’s routing.
When adding a MT Route, the following parameters are required:
• type: One of the supported MT Routes: DefaultRoute, StaticMTRoute, RandomRoundrobinMTRoute
jcli : mtrouter -a
Adding a new MT Route: (ok: save, ko: exit)
> type DefaultRoute
jasmin.routing.Routes.DefaultRoute arguments:
connector
> connector smppc(smppcc_default)
> rate 0.0
> ok
Successfully added MTRoute [DefaultRoute] with order:0
Note: You don’t have to set order parameter when the MT Route type is DefaultRoute, it will be automatically set
to 0
jcli : mtrouter -a
Adding a new MT Route: (ok: save, ko: exit)
> type StaticMTRoute
jasmin.routing.Routes.StaticMTRoute arguments:
filters, connector
> filters filter_1;filter_2
> order 10
> connector smppc(smppcc_1)
> rate 0.0
> ok
Successfully added MTRoute [StaticMTRoute] with order:10
Here’s an example of adding a RandomRoundrobinMTRoute to two SMPP Client Connectors (smppcc_2 and smp-
pcc_3):
jcli : mtrouter -a
Adding a new MT Route: (ok: save, ko: exit)
> order 20
> type RandomRoundrobinMTRoute
jasmin.routing.Routes.RandomRoundrobinMTRoute arguments:
filters, connectors
> filters filter_3
> connectors smppc(smppcc_2);smppc(smppcc_3)
> rate 0.0
> ok
Successfully added MTRoute [RandomRoundrobinMTRoute] with order:20
Here’s an example of adding a FailoverMTRoute to two SMPP Client Connectors (smppcc_4 and smppcc_5):
jcli : mtrouter -a
Adding a new MT Route: (ok: save, ko: exit)
> order 30
> type FailoverMTRoute
jasmin.routing.Routes.FailoverMTRoute arguments:
(continues on next page)
Once the above MT Routes are added to MTRoutingTable, it is possible to list these routes:
jcli : mtrouter -l
#Order Type Rate Connector ID(s) Filter(s)
#20 FailoverMTRoute 0 (!) smppc(smppcc_3), smppc(smppcc_4) <T>
#20 RandomRoundrobinMTRoute 0 (!) smppc(smppcc_2), smppc(smppcc_3) <T>
#10 StaticMTRoute 0 (!) smppc(smppcc_1) <T>, <T>
#0 DefaultRoute 0 (!) smppc(smppcc_default)
Total MT Routes: 3
Note: Filters and Connectors were created before creating these routes, please check Filter manager and HTTP Client
connector manager for further details
jcli : mtrouter -s 20
RandomRoundrobinMTRoute to 2 connectors:
- smppc(smppcc_2)
- smppc(smppcc_3)
NOT RATED
jcli : mtrouter -s 10
StaticMTRoute to smppc(smppcc_1) NOT RATED
jcli : mtrouter -s 0
DefaultRoute to smppc(smppcc_default) NOT RATED
The MO Interceptor manager module is accessible through the mointerceptor command and is providing the follow-
ing features:
Note: MO Interceptor is used to hand inbound messages (SMS MO) to a user defined script, check Interception for
more details.
MO Interceptor helps managing Jasmin’s MOInterceptionTable, which is responsible of intercepting SMS MO before
routing is made, here are the basics of Jasmin MO interception mechanism:
1. MOInterceptionTable holds ordered MOInterceptor objects (each MOInterceptor has a unique order)
2. A MOInterceptor is composed of:
• Filters: One or many filters (c.f. Filter manager)
• Script: Path to python script
3. There’s many objects inheriting MOInterceptor to provide flexible ways to route messages:
• DefaultInterceptor: An interceptor without a filter, this one can only set with the lowest order to be a
default/fallback interceptor
• StaticMOInterceptor: A basic interceptor with Filters and one Script
4. When a SMS MO is received, Jasmin will ask for the right MOInterceptor to consider, all interceptors are
checked in descendant order for their respective Filters (when a MOInterceptor have many filters, they are
checked with an AND boolean operator)
5. When a MOInterceptor is considered (its Filters are matching a received SMS MO), Jasmin will call its Script
with the Routable argument.
Check Interception for more details about Jasmin’s interceptor.
When adding a MO Interceptor, the following parameters are required:
• type: One of the supported MO Interceptors: DefaultInterceptor, StaticMOInterceptor
• order: MO Interceptor order
When choosing the MO Interceptor type, additional parameters may be added to the above required parameters.
Here’s an example of adding a DefaultInterceptor to a python script:
jcli : mointerceptor -a
Adding a new MO Interceptor: (ok: save, ko: exit)
> type DefaultInterceptor
<class 'jasmin.routing.Interceptors.DefaultInterceptor'> arguments:
(continues on next page)
Note: Pay attention that the given script is copied to Jasmin core, do not expect Jasmin to refresh the script code
when you update it, you’ll need to redefine the mointerceptor rule again so Jasmin will refresh the script.
Note: You don’t have to set order parameter when the MO Interceptor type is DefaultInterceptor, it will be auto-
matically set to 0
jcli : mointerceptor -a
Adding a new MO Interceptor: (ok: save, ko: exit)
> type StaticMOInterceptor
<class 'jasmin.routing.Interceptors.StaticMOInterceptor'> arguments:
filters, script
> order 10
> filters filter_1
> script python3(/opt/jasmin-scripts/interception/mo-interceptor.py)
> ok
Successfully added MOInterceptor [StaticMOInterceptor] with order:10
Once the above MO Interceptors are added to MOInterceptionTable, it is possible to list these interceptors:
jcli : mointerceptor -l
#Order Type Script Filter(s)
#10 StaticMOInterceptor <MOIS (pyCode= ..)> <T>
#0 DefaultInterceptor <MOIS (pyCode= ..)>
Total MO Interceptors: 2
Note: Filters were created before creating these interceptors, please check Filter manager for further details
jcli : mointerceptor -s 10
StaticMOInterceptor/<MOIS (pyCode= ..)>
jcli : mointerceptor -s 0
DefaultInterceptor/<MOIS (pyCode= ..)>
The MT Interceptor manager module is accessible through the mtinterceptor command and is providing the following
features:
Note: MT Interceptor is used to hand outbound messages (SMS MT) to a user defined script, check Interception for
more details.
MT Interceptor helps managing Jasmin’s MTInterceptionTable, which is responsible of intercepting SMS MT before
routing is made, here are the basics of Jasmin MT interception mechanism:
1. MTInterceptionTable holds ordered MTInterceptor objects (each MTInterceptor has a unique order)
2. A MTInterceptor is composed of:
• Filters: One or many filters (c.f. Filter manager)
• Script: Path to python script
3. There’s many objects inheriting MTInterceptor to provide flexible ways to route messages:
• DefaultInterceptor: An interceptor without a filter, this one can only set with the lowest order to be a
default/fallback interceptor
• StaticMTInterceptor: A basic interceptor with Filters and one Script
4. When a SMS MT is received, Jasmin will ask for the right MTInterceptor to consider, all interceptors are
checked in descendant order for their respective Filters (when a MTInterceptor have many filters, they are
checked with an AND boolean operator)
5. When a MTInterceptor is considered (its Filters are matching a received SMS MT), Jasmin will call its Script
with the Routable argument.
Check Interception for more details about Jasmin’s interceptor.
When adding a MT Interceptor, the following parameters are required:
• type: One of the supported MT Interceptors: DefaultInterceptor, StaticMTInterceptor
• order: MT Interceptor order
When choosing the MT Interceptor type, additional parameters may be added to the above required parameters.
Here’s an example of adding a DefaultInterceptor to a python script:
jcli : mtinterceptor -a
Adding a new MT Interceptor: (ok: save, ko: exit)
> type DefaultInterceptor
<class 'jasmin.routing.Interceptors.DefaultInterceptor'> arguments:
script
> script python3(/opt/jasmin-scripts/interception/mt-interceptor.py)
> ok
Successfully added MTInterceptor [DefaultInterceptor] with order:0
Note: Pay attention that the given script is copied to Jasmin core, do not expect Jasmin to refresh the script code
when you update it, you’ll need to redefine the mtinterceptor rule again so Jasmin will refresh the script.
Note: You don’t have to set order parameter when the MT Interceptor type is DefaultInterceptor, it will be auto-
matically set to 0
jcli : mtinterceptor -a
Adding a new MT Interceptor: (ok: save, ko: exit)
> type StaticMTInterceptor
<class 'jasmin.routing.Interceptors.StaticMTInterceptor'> arguments:
filters, script
> order 10
> filters filter_1
> script python3(/opt/jasmin-scripts/interception/mt-interceptor.py)
> ok
Successfully added MTInterceptor [StaticMTInterceptor] with order:10
Once the above MT Interceptors are added to MTInterceptionTable, it is possible to list these interceptors:
jcli : mtinterceptor -l
#Order Type Script Filter(s)
#10 StaticMTInterceptor <MTIS (pyCode= ..)> <T>
#0 DefaultInterceptor <MTIS (pyCode= ..)>
Total MT Interceptors: 2
Note: Filters were created before creating these interceptors, please check Filter manager for further details
jcli : mtinterceptor -s 10
StaticMTInterceptor/<MTIS (pyCode= ..)>
jcli : mtinterceptor -s 0
DefaultInterceptor/<MTIS (pyCode= ..)>
The SMPP Client connector manager module is accessible through the smppccm command and is providing the
following features:
A SMPP Client connector is used to send/receive SMS through SMPP v3.4 protocol, it is directly connected to MO
and MT routers to provide end-to-end message delivery.
Adding a new SMPP Client connector requires knowledge of the parameters detailed in the listing below:
Note: When adding a SMPP Client connector, only it’s cid is required, all the other parameters will be set to their
respective defaults.
Note: Connector restart is required only when changing the following parameters: host, port, username, password,
systemType, logfile, loglevel; any other change is applied without requiring connector to be restarted.
Here’s an example of adding a new transmitter SMPP Client connector with cid=Demo:
jcli : smppccm -a
Adding a new connector: (ok: save, ko: exit)
> cid Demo
> bind transmitter
> ok
Successfully added connector [Demo]
Note: From the example above, you can see that showing a connector details will return all it’s parameters even those
you did not enter while creating/updating the connector, they will take their respective default values as explained in
Listing connectors will show currently added SMPP Client connectors with their CID, Service/Session state and
start/stop counters:
jcli : smppccm -l
#Connector id Service Session Starts Stops
#888 stopped None 0 0
#Demo stopped None 0 0
Total connectors: 2
Updating an existent connector is the same as creating a new one, simply type smppccm -u <cid> where cid is the
connector id you want to update, you’ll run into a new interactive session to enter the parameters you want to update
(c.f. SMPP Client connector parameters).
Here’s an example of updating SMPP Client connector’s host:
The Filter manager module is accessible through the filter command and is providing the following features:
Filters are used by MO/MT routers to help decide on which route a message must be delivered, the following flowchart
provides details of the routing process:
Jasmin provides many Filters offering advanced flexibilities to message routing:
Check The message router for more details about Jasmin’s routing.
When adding a Filter, the following parameters are required:
• type: One of the supported Filters: TransparentFilter, ConnectorFilter, UserFilter, GroupFilter, SourceAddrFil-
ter, DestinationAddrFilter, ShortMessageFilter, DateIntervalFilter, TimeIntervalFilter, TagFilter, EvalPyFilter
• fid: Filter id (must be unique)
When choosing the Filter type, additional parameters may be added to the above required parameters:
jcli : filter -a
Adding a new Filter: (ok: save, ko: exit)
type fid
> type transparentfilter
> fid TF
> ok
Successfully added Filter [TransparentFilter] with fid:TF
jcli : filter -a
Adding a new Filter: (ok: save, ko: exit)
> type sourceaddrfilter
jasmin.routing.Filters.SourceAddrFilter arguments:
source_addr
> source_addr ^20\d+
> ok
You must set these options before saving: type, fid, source_addr
> fid From20*
> ok
Successfully added Filter [SourceAddrFilter] with fid:From20*
In addition to predefined filters listed above (Filter manager), it is possible to extend filtering with external scripts
written in Python using the EvalPyFilter.
Here’s a very simple example where an EvalPyFilter is matching the connector cid of a message:
# File @ /opt/jasmin-scripts/routing/abc-connector.py
if routable.connector.cid == 'abc':
result = True
else:
result = False
jcli : filter -a
Adding a new Filter: (ok: save, ko: exit)
> type EvalPyFilter
jasmin.routing.Filters.EvalPyFilter arguments:
pyCode
> pyCode /opt/jasmin-scripts/routing/abc-connector.py
> fid SimpleThirdParty
> ok
Successfully added Filter [EvalPyFilter] with fid:SimpleThirdParty
This example will provide an EvalPyFilter (SimpleThirdParty) that will match any message coming from the connec-
tor with cid = abc.
Using EvalPyFilter is as simple as the shown example, when the python script is called it will get the following global
variables set:
• routable: one of the jasmin.routing.Routables.Routable inheriters (Routable for more details)
• result: (default to False) It will be read by Jasmin router at the end of the script execution to check if the filter
is matching the message passed through the routable variable, matched=True / unmatched=False
Note: It is possible to check for any parameter of the SMPP PDU: TON, NPI, PROTOCOL_ID . . . since it is provided
through the routable object.
Note: Using EvalPyFilter offers the possibility to call external webservices, databases . . . for powerfull routing or
even for logging, rating & billing through external third party systems.
Hint: More examples in the this FAQ’s question: Can you provide an example of how to use EvalPyFilter ?
The HTTP Client connector manager module is accessible through the httpccm command and is providing the fol-
lowing features:
A HTTP Client connector is used in SMS-MO routing, it is called with the message parameters when it is returned by
a matched MO Route (Receiving SMS-MO for more details).
When adding a HTTP Client connector, the following parameters are required:
• cid: Connector id (must be unique)
• url: URL to be called with message parameters
• method: Calling method (GET or POST)
Here’s an example of adding a new HTTP Client connector:
jcli : httpccm -a
Adding a new Httpcc: (ok: save, ko: exit)
> url http://10.10.20.125/receive-sms/mo.php
> method GET
> cid HTTP-01
> ok
Successfully added Httpcc [HttpConnector] with cid:HTTP-01
Listing Connectors will show currently added Connectors with their CID, Type, Method and Url:
jcli : httpccm -l
#Httpcc id Type Method URL
#HTTP-01 HttpConnector GET http://10.10.20.125/receive-sms/mo.php
Total Httpccs: 1
The Stats manager module is responsible for showing real time statistics, aggregated counters and values such as
current bound connections of a User, number of http requests, number of sent messages through a Route, Filter,
Connector . . .
Note: All values are collected during Jasmin’s uptime and they are lost when Jasmin goes off, Stats manager shall be
used for monitoring activities but not for advanced business reports.
The Stats manager module is accessible through the stats command and is providing the following features:
The Stats manager covers different sections, this includes Users, SMPP Client connectors, Routes (MO and MT), APIs
(HTTP and SMPP).
User statistics
The Stats manager exposes an overall view of all existent users as well as a per-user information view:
• stats –users: Will show an overall view of all existent users
• stats –user foo: Will show detailed information for foo
Here’s an example of showing an overall view where users sandra and foo are actually having 2 and 6 SMPP bound
connections, user bar is using the HTTP Api only and sandra is using both APIs:
Total users: 3
The columns shown for each user are explained in the following table:
This is clearly a more detailed view for user sandra, the following table explains the items shown for sandra:
The Stats manager exposes an overall view of all existent smppc connectors as well as a per-smppc information view:
• stats –smppcs: Will show an overall view of all existent smppc connectors
• stats –smppc foo: Will show detailed information for foo
Here’s an example of showing an overall view where smppc connectors MTN and ORANGE are actives, connector
SFONE made no activity at all:
jcli : stats --smppcs
#Connector id Connected at Bound at Disconnected at Submits Delivers
˓→QoS errs Other errs
(continues on next page)
The columns shown for each user are explained in the following table:
This is clearly a more detailed view for connector MTN, the following table explains the items shown for MTN:
The Stats manager exposes collected statistics in SMPP Server API through the following jCli command:
• stats –smppsapi
Here’s an example of showing the statistics:
The following table explains the items shown in the above example:
The Stats manager exposes collected statistics in HTTP API through the following jCli command:
• stats –httpapi
Here’s an example of showing the statistics:
The following table explains the items shown in the above example:
3.12 Billing
Jasmin comes with a user billing feature that lets you apply rates on message routes, every time a user sends a SMS
through a rated route he’ll get charged, once he runs out of credit no more sending will be permitted.
Important: New routes created through MT router manager are not rated by default, you must define the rate of each
route in order to enable billing.
Note: Billing is applied on all channels (SMPP Server and HTTP API) the same way.
A user can be charged through 2 types of quotas (balance and/or sms_count), if he reaches the limit of one of these
quotas no more sending will be permitted, no matter the used channel (SMPP Server or HTTP API).
1. Balance quota
The route rate will be charged on the user balance, let’s get into these use cases for better comprehension:
• When sending one SMS through a route rated 1.2, user’s balance will get decreased by 1.2
• When sending five SMS through a route rated 0.2, user’s balance will get decreased by 1
3.12. Billing 93
Jasmin Documentation, Release 0.10.11s
Important: New users created through User manager will have unlimited balance by default, assuming you’ll apply
postpaid billing (or no billing at all), user’s balance must be defined in order to enable billing.
Rate unit
You can see that the rates have no unit or currency, this will offer better flexibility for different business cases, you can
consider the rates as:
• Local Jasmin currency and keep a rate for converting to real-life currency.
• Real-life currency
• etc ..
In all cases, Jasmin will never manage the rate unit (or currency), all it does is to ensure users are correctly charged by
the rates you define.
Asynchronous billing
As explained later, it is important to know that whatever the used protocol, SMS is always sent asynchronously, this
means there’s always an acknowledgment to be received for every sent SMS; Jasmin provides an optional adapted
billing algorithm which is able to charge the user asynchronously:
1. A defined percentage of the route rate is charged when the user submits the SMS for sending.
2. The rest is charged when the SMS is acknowledged by the next relay, in SMPP protocol, this means receiving
SUBMIT_SM_RESP PDU, more details here.
Asynchronous billing is automatically enabled when the user have early_decrement_balance_percent de-
fined (undefined by default), let’s get back to examples for better comprehension, assuming user have
early_decrement_balance_percent = 25:
• When sending one SMS through a route rated 1.2:
• When sending, user’s balance is decreased by 0.3 (1.2 x 25%)
• When acknowledged, user’s balance is decreased by 0.9 (the rest)
• When sending five SMS through a route rated 0.2:
• When sending, user’s balance is decreased by 0.25 (5 x 0.2 x 25%)
• For each acknowledged SMS, user’s balance is decreased by 0.15
• When all five sent messages are acknowledged, the final charged amount is 0.75 (the rest)
Using asynchronous billing can be helpful in many use cases:
• Charge only when the SMS is acknowledged
• If SMS is not acknowledged for some reason, user can not fill Jasmin’s queues by SMS requests indefinitely,
he’ll get out of credits
• etc ..
2. sms_count quota
Simpler than Balance management, sms_count is a counter to be decreased whenever the user submits the SMS for
sending, let’s get into these use cases for better comprehension:
• When sending one SMS through a route, user’s sms_count will get decreased by 1
• When sending five SMS through a route, user’s sms_count will get decreased by 5
Note: When defined, sms_count is always decreased no matter the route is rated or not.
Important: New users created through User manager will have unlimited sms_count by default, assuming you’ll
apply postpaid billing (or no billing at all), user’s sms_count must be defined in order to enable billing (or limit).
The following process flow shows how billing is done through HTTP Api (same process is applied on SMPP Server),
it is including all types of billing:
• balance quota billing (ref ) including asynchronous billing (ref )
• sms_count quota billing (ref )
When enabled, Asynchronous billing algorithm can charge user every time an acknowledgment is received for each
SMS he sent earlier, the following call flow explain the asynchronous billing algorithm:
In the above figure, user is charged early before submitting SMS to SMSC, and the charged later when the SMSC
acknowledge back reception of the message, as detailed earlier, the charged amount in early stage is defined by
early_decrement_balance_percent set in user profile.
Note: The route rate is expressed on a per-SUBMIT_SM basis, submitting a long SMS will be splitted into multiple
submit_sm SMPP PDUs, each one will be charged on user.
The below figure explain how asynchronous billing is handling long content messages, assuming a user is submitting
a message containing 400 characters, which will imply sending 3 submit_sm SMPP PDUs:
Asynchronous billing is mainly relying on AMQP broker (like messaging), The AMQP broker is providing a queuing
mechanism, through the following illustration you can see how asynchronous billing is done:
When receiving a SUBMIT_SM_RESP PDU, submit_sm_resp_event() method is called (more details here), it will
check if there’s a remaining bill to charge on user and publish it on bill_request.submit_sm_resp.UID (using billing
exchange) where UID is the concerned User ID.
RouterPB’s bill_request_submit_sm_resp_callback() is listening on the same topic and it will be fired whenever it
consumes a new bill request, as the Router is holding User objects in memory, it will simply update their balances
with the bill amount.
Jasmin is doing everything in-memory for performance reasons, including User charging where the balance must
be persisted to disk for later synchronization whenever Jasmin is restarted, this is why RouterPB is automatically
persisting Users and Groups to disk every persistence_timer_secs seconds as defined in jasmin.cfg file (INI format,
located in /etc/jasmin).
3.12. Billing 95
Jasmin Documentation, Release 0.10.11s
3.12. Billing 97
Jasmin Documentation, Release 0.10.11s
Fig. 12: Asynchronous billing call flow for long content messages
3.12. Billing 99
Jasmin Documentation, Release 0.10.11s
Important: Set persistence_timer_secs to a reasonable value, keep in mind that every disk-access operation will
cost you few performance points, and don’t set it too high as you can loose Users balance data updates.
Messaging is heavily relying on an AMQP broker using topics to queue messages for routing, delivering and acking
back.
The AMQP broker is providing a strong store & forward queuing mechanism, through the following illustration you
can see how every messaging component is asynchronously connected to the broker.
3.13.1 SMPPClientManagerPB
This is a PerspectiveBroker (PB) responsible of managing SMPP Client connectors (list, add, remove, start, stop, send
SMS, etc . . . ), we’ll be only covering the latter (Send SMS).
When the perspective_submit_sm() is called with a SubmitSm PDU and destination connector ID, it will build an
AMQP Content message and publish it to a queue named submit.sm.CID where CID is the destination connector ID.
Note: perspective_submit_sm() is called from HTTP API and SMPP Server API after they check with RouterPB
for the right connector to send a SubmitSM to.
Every SMPP Connector have a consumer waiting for these messages, once published as explained above, it will be
consumed by the destination connector’s submit_sm_callback() method (c.f. SMPPClientSMListener).
3.13.2 DLRLookup
This is a consumer on the dlr.* AMQP route, added in v0.9, it’s main role is DLR map fetching from Redis database
and publishing the dlr to the right thrower (http or smpp).
3.13.3 RouterPB
This is another PerspectiveBroker (PB) responsible of routing DeliverSm messages, these are received through the
SMPP client connector’s deliver_sm_event_interceptor() method (c.f. SMPPClientSMListener) which publish to de-
liver.sm.CID, the RouterPB main role is to decide whether to route DeliverSm messages to:
• deliver_sm_thrower.smpps: if the message is to be delivered through SMPP Server API.
• deliver_sm_thrower.http: if the message is to be delivered through a HTTP connector.
3.13.4 SMPPClientSMListener
Every SMPP Client connector have one attached SMPPClientSMListener instance, it is responsible for handling mes-
sages exchanged through the SMPP Client connector using the following event catchers:
deliver_sm_event_interceptor
Every received DeliverSm PDU is published directly to the broker with the following assumptions:
• If it’s a SMS-MO message it will get published as an AMQP Content message to deliver_sm.CID where CID
is the source connector ID, this message will be handled by the RouterPB.
• If it’s a delivery receipt and if it were requested when sending the SubmitSm, it will get published as an AMQP
Content message to dlr_thrower.http or dlr_thrower.smpps (depends on the used channel for sending initial
SubmitSM) for later delivery by DLRThrower’s dlr_throwing_callback() method.
Note: deliver_sm_event_interceptor() will check for interception rules before proceding to routing, c.f. Interception
for more details.
submit_sm_callback
It is a simple consumer of submit.sm.CID where CID is its connector ID, it will send every message received through
SMPP connection.
submit_sm_resp_event
It is called for every received SubmitSmResp PDU, will check if the related SubmitSm was requiring a delivery receipt
and will publish it (or not) to dlr_thrower.http or dlr_thrower.smpps (depends on the used channel for sending initial
SubmitSM).
Note: There’s no actual reason why messages are published to submit.sm.resp.CID, this may change in future.
3.13.5 deliverSmThrower
This is will through any received message from deliver_sm_thrower.http to its final http connector, c.f. Receiving
SMS-MO for details and from deliver_sm_thrower.smpps to its final SMPP Server binding.
3.13.6 DLRThrower
This is will through any received delivery receipt from dlr_thrower.http to its final http connector, c.f. Receiving
DLR for details and from dlr_thrower.smpps to its final SMPP Server binding.
3.14.1 Could not find a version that satisfies the requirement jasmin
Cleaning up...
No distributions matching the version for jasmin
Storing debug log for failure in /home/richard/.pip/pip.log
This is common question, since Jasmin is still tagged as a ‘Beta’ version, pip installation must be done with the –pre
parameter:
According to the installation guide, Jasmin requires running RabbitMQ and Redis servers, when starting it will wait
for these servers to go up.
If you already have these requirements, please check jcli and redis-client logs:
• /var/log/jasmin/redis-client.log
• /var/log/jasmin/jcli.log
3.14.3 Should i expose my SMPP Server & HTTP API to the public internet for re-
mote users ?
As a security best practice, place Jasmin instance(s) behind a firewall and apply whitelisting rules to only accept users
you already know, a better solution is to get VPN tunnels with your users.
If for some reasons you cannot consider these practices, here’s a simple iptables configuration that can help to prevent
Denial-of-service attacks:
iptables -I INPUT -p tcp --dport 2775 -m state --state NEW -m recent --set --name
˓→SMPP_CONNECT
iptables -N RULE_SMPP
iptables -I INPUT -p tcp --dport 2775 -m state --state NEW -m recent --update --
˓→seconds 60 --hitcount 3 --name SMPP_CONNECT -j RULE_SMPP
This will drop any SMPP Connection request coming from the same source IP with more than 3 times per minute . . .
Since everything in Jasmin runs fully in-memory, what will happen if i restart Jasmin or if it crashes for some reason
? how can i ensure my configuration (Connectors, Users, Routes, Filters . . . ) will be reloaded with the same state they
were in before Jasmin goes off ?
Jasmin is doing everything in-memory for performance reasons, and is automatically persisting newly updated config-
urations every persistence_timer_secs seconds as defined in jasmin.cfg file.
Important: Set persistence_timer_secs to a reasonable value, keep in mind that every disk-access operation will
cost you few performance points, and don’t set it too high as you can loose critical updates such as User balance
updates.
The following error may appear in messages.log while receiving a receipt (DLR):
What’s happening:
When sending a message (submit_sm) the upstream connector will reply back with a first receipt (submit_sm_resp)
where it indicates the message id for further tracking, then it will send back another receipt (deliver_sm or data_sm)
with the same message it and different delivery state. The problem occurs when the upstream connector returns the
same message id but in different encodings.
Solution:
Use the dlr_msgid parameter as shown in SMPP Client connector manager to indicate the encoding strategy of the
upstream partner/connector.
3.14.6 How to hide message content in log files for privacy reasons ?
Starting from v0.9.28 it is possible to hide the message content in log files, this is done by tweaking the log_privacy
parameter in the SMPP Client connector manager and log_privacy in jasmin.cfg and dlrlookupd.cfg.
Jasmin runs without a database, everything is in-memory and messages are exchanged through AMQP broker (Rab-
bitMQ), if you need to get these messages you have to consume from the right queues as described in Messaging
flows.
Here’s an example:
Thanks to Pedro’s contribution:
Cheers
Pedro
@inlineCallbacks
def gotConnection(conn, username, password):
print("Connected to broker.")
yield conn.authenticate(username, password)
yield chan.queue_declare(queue="someQueueName")
if msg.routing_key[:15] == 'submit.sm.resp.':
print('SubmitSMResp: status: %s, msgid: %s' % (pdu.status,)
props['message-id'])
elif msg.routing_key[:10] == 'submit.sm.':
print('SubmitSM: from %s to %s, content: %s, msgid: %s' % (pdu.params[
˓→'source_addr'],)
pdu.params['destination_addr'],
pdu.params['short_message'],
props['message-id'])
else:
print('unknown route')
reactor.stop()
if __name__ == "__main__":
"""
This example will connect to RabbitMQ broker and consume from two route keys:
- submit.sm.*: All messages sent through SMPP Connectors
- submit.sm.resp.*: More relevant than SubmitSM because it contains the sending
˓→status
(continues on next page)
Note:
- Messages consumed from submit.sm.resp.* are not verbose enough, they contain
˓→only message-id and status
- Message content can be obtained from submit.sm.*, the message-id will be the
˓→same when consuming from submit.sm.resp.*,
"""
host = '127.0.0.1'
port = 5672
vhost = '/'
username = 'guest'
password = 'guest'
spec_file = '/etc/jasmin/resource/amqp0-9-1.xml'
spec = txamqp.spec.load(spec_file)
def whoops(err):
if reactor.running:
log.err(err)
reactor.stop()
d.addErrback(whoops)
reactor.run()
Management tasks can be done directly when accessing PerspectiveBroker API, it will be possible to:
• Manage SMPP Client connectors,
• Check status of all connectors,
• Send SMS,
• Manage Users & Groups,
• Manage Routes (MO / MT),
• Access statistics,
• ...
Here’s an example:
@defer.inlineCallbacks
def runScenario():
try:
## First part, SMPP Client connector management
###############################################
# Connect to SMPP Client management PB proxy
proxy_smpp = SMPPClientManagerPBProxy()
yield proxy_smpp.connect('127.0.0.1', 8989, 'cmadmin', 'cmpwd')
config1 = SMPPClientConfig(**connector1)
yield proxy_smpp.add(config1)
yield proxy_smpp.start('abc')
runScenario()
reactor.run()
Let’s say you need your filter to pass only messages from username foo:
if routable.user.username == 'foo':
result = False
else:
result = True
Note: Although UserFilter is already there to provide this feature, this is just a simple example of using EvalPyFilter.
So your python script will have a routable global variable, it is an instance of RoutableDeliverSm if you’re playing
with a MO Route and it will be an instance of RoutableSubmitSm if you’re considering it with a MT Route.
In order to implement your specific filter, you have to know all the attributes these objects are providing,
Now let’s make an advanced example, the below filter will:
• Connect to a database
• Check if the message destination_address is in blacklisted_numbers table
• Pass only if the destination_address is not blacklisted
"""This is an example of using EvalPyFilter with a database interrogation, it is
˓→written
destination_addr = routable.pdu.params['destination_addr']
try:
con = mdb.connect('localhost', 'jasmin', 'somepassword', 'jasmin_faq');
cur = con.cursor()
cur.execute("SELECT COUNT(msisdn) FROM blacklisted_numbers WHERE msisdn = %s"
˓→% destination_addr)
count = cur.fetchone()
if count[0] == 0:
# It is not blacklisted, filter will pass
(continues on next page)
It is a usual method to get the filter logging directly to the Router’s log file (default is router.log), here’s a very simple
example of doing it:
import logging
log = logging.getLogger("jasmin-router")
log.debug('Inside evalpy-test.py')
if routable.user.username == 'Evalpyusr2':
log.info("Routable's username is Evalpyusr2 !")
result = False
else:
log.info("Routable's username is not Evalpyusr2: %s" % routable.user.username)
result = True
jcli : filter -a
Adding a new Filter: (ok: save, ko: exit)
> type evalpyfilter
> pyCode /some/path/advanced_evalpyfilter.py
> fid blacklist_check
> ok
Successfully added Filter [EvalPyFilter] with fid:blacklist_check
jcli : mtrouter -a
Adding a new MT Route: (ok: save, ko: exit)
> type StaticMTRoute
jasmin.routing.Routes.StaticMTRoute arguments:
filters, connector, rate
> filters blacklist_check
(continues on next page)
And you’re done ! test your filter by sending a SMS through Jasmin’s APIs.
3.15.6 PDU params keep resetting to connector defaults even after interception ?
When sending MT messages through httpapi, some pdu parameters will be reset to connector defaults even if they
were manually updated inside an interceptor script, how can Jasmin avoid updatingmy pdu params ?
After updating a pdu parameter, it must be locked so Jasmin will not re-update it again, here’s an example:
Note: Locking pdu parameters is only needed when message is pushed from httpapi.
Links
111
Jasmin Documentation, Release 0.10.11s
License
Jasmin is released under the terms of the [Apache License Version 2]. See ‘LICENSE‘ file for details.
113