Article image

HOW TO SEND AND RECEIVE SMS MESSAGES WITH PHP USING SMPP

Laravel -

Aug 05 2020

Dino Numic

SMPP (Short Message Peer-to-Peer) protocol is an open source protocol that allows us to send short messages between applications and mobile devices (receiving and sending).

We will use this package SMPP package https://github.com/onlinecity/php-smpp.

Let’s require it.

composer require php-smpp/php-smpp

If you run into this problem:

Find the following line in php.ini 

;extension=sockets

And remove ; in front of it.

Let’s start creating our smpp service.

Create a new folder inside the app called Services. Inside Services create the Smpp folder. Inside Smpp folder, we will create two classes: SmppReceiver and SmppTransmitter.

SmppReceiver will handle all incoming messages. Messages sent by our customers. Here we can do various logic of storing users and messages, validation, and etc.

SmppTransmitter will handle all outbound messages. Here we can send messages to other users.

We will use this https://melroselabs.com/services/smsc-simulator/ to test our smpp connection. We need to populate our env variables with values from the link.

SMPP_SERVICE=
SMPP_PORT=
SMPP_RECEIVER_ID=
SMPP_RECEIVER_PASSWORD=
SMPP_TRANSMITTER_ID=
SMPP_TRANSMITTER_PASSWORD=

We will also create a config file from where we will retrieve values from env. Create smpp.php file in config.

Add the following:

<?php

return [
   'smpp_service' => env('SMPP_SERVICE'),
   'smpp_port' => env('SMPP_PORT'),
   'smpp_receiver_id' => env('SMPP_RECEIVER_ID'),
   'smpp_receiver_password' => env('SMPP_RECEIVER_PASSWORD'),
   'smpp_transmitter_id' => env('SMPP_TRANSMITTER_ID'),
   'smpp_transmitter_password' => env('SMPP_TRANSMITTER_PASSWORD'),
];

Now run php artisan config:cache.

Let’s first work on SmppReceiver.

This is the starting layout of our receiver class.

<?php

namespace App\Services\Smpp;
use SmppClient;
use SmppDeliveryReceipt;
use SocketTransport;

class SmppReceiver
{
   protected $transport, $client, $transmitter;

   public function start() {}
   protected function connect() {}
   protected function disconnect() {}
   protected function keepAlive() {}
   protected function readSms() {}
}

Connect method

protected function connect()
{
   // Create transport
   $this->transport = new SocketTransport([config('smpp.smpp_service')], config('smpp.smpp_port'));
   $this->transport->setRecvTimeout(30000);
   $this->transport->setSendTimeout(30000);

   // Create client
   $this->client = new SmppClient($this->transport);

   // Activate binary hex-output of server interaction
   $this->client->debug = true;
   $this->transport->debug = true;

   // Open the connection
   $this->transport->open();

   // Bind receiver
   $this->client->bindReceiver(config('smpp.smpp_receiver_id'), config('smpp.smpp_receiver_id'));
}

Disconnect method

protected function disconnect()
{
   if (isset($this->transport) && $this->transport->isOpen()) {
      if (isset($this->client)) {
          try {
             $this->client->close();
          } catch (\Exception $e) {
             $this->transport->close();
          }
      } else {
          $this->transport->close();
      }
   }
}

Keep Alive method

protected function keepAlive()
{
   $this->client->enquireLink();
   $this->client->respondEnquireLink();
}

Read sms method

protected function readSms(){
        $time_start = microtime(true);
//        $endtime = $time_start + 43200;  // 12 h
//        $endtime = $time_start + 21600;  //  6 h
//        $endtime = $time_start + 1800;     //  30 m
        $endtime = $time_start + 120;    //  2 m
        $lastTime = 0;

        do {
            $res = $this->client->readSMS();
            if ($res) {
                try {
                    if ($res instanceof SmppDeliveryReceipt) {
                        // If enabled sms provider will send us a delivery report of the sms we sent to client with its status
                       /**0    SCHEDULED
                          1    ENROUTE
                          2    DELIVERED
                          3    EXPIRED
                          4    DELETED
                          5    UNDELIVERABLE
                          6    ACCEPTED
                          7    UNKNOWN
                          8    REJECTED
                          9    SKIPPED
                         */

                    } else {
                        $from = $res->source->value;     // Number from which the number was sent
                        $to = $res->destination->value;  // Receiving number
                        $message = $res->message;        // Content of the message

                        dd($from, $message, $to);
                    }

                } catch (\Exception $e) {
                    // Something went wrong while reading message
                    Log::error($e);
                }
            }

            // Keep connection alive every 30 secondsif (time()-$lastTime >= 30) {$this->keepAlive();
                $lastTime = time();
            } else {
                $this->client->respondEnquireLink();
            }
        } while ($endtime > microtime(true));
    }

This function will loop for as long as you set it. It will listen to all incoming messages. A message can either be a message from the user or a delivery report for the message we sent from our transmitter (we don’t have it yet). Telecom providers, if they have this feature enabled, will send delivery statuses for the messages we send. It is good to know whether our messages are sent correctly.

Start method

public function start()
{
   $this->connect();
   $this->readSms();
}

This is the main method we call in our receiver class. It will open up a connection and start the process of listening to incoming messages.

Let’s try sending a message to ourselves and see whether it gets through. For now, we can test our receiver service inside web routes.

Route::get('/', function () {
   $receiver = new \App\Services\Smpp\SmppReceiver();
   $receiver->start();
});

On the right side we have the option to customize the message and a button to send.

Open 127.0.0.1:8000 (url on which you are hosting laravel app). While it’s listening to messages press the send button. If everything went correctly you should see the following. Output from dd() in our readSms() method.

It would also be good if we could send messages to phone numbers or respond to our customers. It is time to start working on our transmitter.

Layout of our transmitter class

<?php

namespace App\Services\Smpp;
use GsmEncoder;
use Illuminate\Support\Facades\Log;
use SMPP;
use SmppAddress;
use SmppClient;
use SocketTransport;

class SmppTransmitter
{
   protected $transport, $client, $credentialTransmitter;

   public function __construct()
   {
       $this->connect();
   }

   protected function connect() {}

   protected function disconnect() {}

   public function keepAlive() {}

   public function respond() {}

   public function sendSms($message, $from, $to) {}
}

Connect method

protected function connect()
{
   // Create transport
   $this->transport = new SocketTransport([config('smpp.smpp_service')], config('smpp.smpp_port'));
   $this->transport->setRecvTimeout(30000);
   $this->transport->setSendTimeout(30000);

   // Create client
   $this->client = new SmppClient($this->transport);

   // Activate binary hex-output of server interaction
   $this->client->debug = true;
   $this->transport->debug = true;

   // Open the connection
   $this->transport->open();

   // Bind transmitter
   $this->client->bindTransmitter(config('smpp.smpp_transmitter_id'), config('smpp.smpp_transmitter_password'));
}

Disconnect method

protected function disconnect()
{
   if (isset($this->transport) && $this->transport->isOpen()) {
       if (isset($this->client)) {
           try {
               $this->client->close();
           } catch (\Exception $e) {
               $this->transport->close();
           }
       } else {
           $this->transport->close();
       }
   }
}

Keep alive and respond methods

public function keepAlive()
{
   $this->client->enquireLink();
   $this->client->respondEnquireLink();
}

public function respond()
{
   $this->client->respondEnquireLink();
}

Send sms method

public function sendSms($message, $from, $to)
{
   // Check if all parameters present
   if (!isset($message) || !isset($from) || !isset($to)) {
       // Handle missing parameters
   }

   // Encode parameters
   $encodedMessage = GsmEncoder::utf8_to_gsm0338($message);
   $fromAddress = new SmppAddress($from, SMPP::TON_ALPHANUMERIC);
   $toAddress = new SmppAddress($to, SMPP::TON_INTERNATIONAL, SMPP::NPI_E164);

   // Try to send message and catch exception
   try {
       $this->client->sendSMS($fromAddress, $toAddress, $encodedMessage);
       return;
   } catch (\Exception $e) {
       Log::error($e);
       Log::error($e->getMessage());
       // Handle failed message send
   }
}

In order to test our transmitter we need to simultaneously run both receiver and transmitter. Currently, with our setup, this is impossible since we are running transmitter from a web route and it blocks all other operations until the loop stops. 

In order to solve this, we need to start receiver from a command and then run transmitter from anywhere we want.

Let’s create our command. php artisan make:command StartSmpp

<?php

namespace App\Console\Commands;
use Illuminate\Console\Command;

class StartSmpp extends Command
{
   /**
    * The name and signature of the console command.
    *
    * @var string
    */
   protected $signature = 'smpp:start-receiver';

   /**
    * The console command description.
    *
    * @var string
    */
   protected $description = 'Start SMPP Receiver';

   /**
    * Create a new command instance.
    *
    * @return void
    */
   public function __construct()
   {
       parent::__construct();
   }

   /**
    * Execute the console command.
    *
    * @return mixed
    */
   public function handle()
   {
       $receiver = new \App\Services\Smpp\SmppReceiver();
       $receiver->start();
   }
}

We just moved our receiver instance from web route to here. Now, when we run this command, it will start SmppReceiver without blocking execution of our app.

Now we can replace receiver with transmitter.

Route::get('/', function () {
   $transmitter = new \App\Services\Smpp\SmppTransmitter();
   $transmitter->sendSms('Hello from transmitter :)', '123456', '00516881');
});

Third parameter needs to be the same as System ID because we are sending the message to ourselves. In a production version of the application this would be a user phone number.

Start command with php artisan smpp:start-receiver. While it’s running refresh the same page we used so far. 127.0.0.1:8000

If everything is working you should see among other things, this output in your terminal.


That is all regarding the code that you need in order to setup SMPP with PHP. However, in order to use this in production you will need a proper SMPP service provider or a deal with Telecom Provider. 

Have a nice day :)