tekkenking/swissecho

A multigateway laravel SMS notification channel package

Maintainers

Package info

github.com/tekkenking/swissecho

pkg:composer/tekkenking/swissecho

Statistics

Installs: 469

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 1

0.7.3 2026-04-19 00:31 UTC

README

What is Swissecho?

Swissecho is a Laravel package that provides a unified, fluent API for sending messages across multiple channels and multiple gateway providers. Instead of writing separate integration code for each provider, you configure them all in one place and switch between them with a single method call.

Supported Channels (Routes)

Channel Description Supported Gateways
SMS Traditional text messages Termii, RouteMobile, SmsBroadcast (AU), TNZ (NZ), NigerianBulkSMS, Montnets, Wirepick
Voice Voice OTP / voice calls Termii, Textng.xyz
WhatsApp WhatsApp messaging KudiSMS
Slack Slack notifications Built-in Slack route

Key Features

  • ๐Ÿ”€ Multi-gateway โ€” Switch SMS providers per-request or per-country
  • ๐ŸŒ Geo-routing (Places) โ€” Automatically route messages to the correct gateway based on the recipient's country
  • ๐Ÿงช Mock mode โ€” In development, messages are logged to file or sent to email instead of hitting live APIs
  • ๐Ÿ”” Laravel Notification integration โ€” Use it as a standard Laravel notification channel
  • โšก Direct sending โ€” Send messages without creating a Notification class
  • ๐Ÿ“ฃ Events โ€” An AfterSend event is dispatched after every message, giving you full insight into requests and responses
  • ๐Ÿช Webhooks โ€” Built-in webhook handling for provider callbacks (e.g., delivery reports)

Requirements

Dependency Supported Versions
PHP ^8.1
Laravel 11.x, 12.x, 13.x

Installation

composer require tekkenking/swissecho

The package auto-discovers itself via Laravel's package auto-discovery โ€” no manual registration needed.

Configuration

Environment Variables

Add these to your .env file. Only configure the gateways you plan to use:

# โ”€โ”€ Core Settings โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
SWISSECHO_ENABLED=false        # Set to true for live/production sending
SWISSECHO_SENDER=MyApp         # Default sender name
SWISSECHO_FAKE=log             # Mock mode: "log" (writes to file) or "mail" (sends email)
SWISSECHO_FAKE_MAIL=admin@example.com  # Email for mock mode when SWISSECHO_FAKE=mail
SWISSECHO_ROUTE=sms            # Default route/channel: sms, voice, whatsapp, slack

# โ”€โ”€ Termii (SMS & Voice) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
TERMII_API_KEY=your_api_key
TERMII_SENDER_ID=YourSender
TERMII_URL=https://api.ng.termii.com/api/sms/send

# โ”€โ”€ RouteMobile โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ROUTEMOBILE_USERNAME=your_username
ROUTEMOBILE_PASSWORD=your_password
ROUTEMOBILE_SENDER_ID=YourSender
ROUTEMOBILE_URL=https://api.routemobile.com/...

# โ”€โ”€ SmsBroadcast (Australia) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
SMSBRC_DOTCOM_DOT_AU_USERNAME=your_username
SMSBRC_DOTCOM_DOT_AU_PASSWORD=your_password
SMSBRC_DOTCOM_DOT_AU_URL=https://api.smsbroadcast.com.au/...

# โ”€โ”€ TNZ (New Zealand) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
TNZ_API_KEY=your_api_key
TNZ_URL=https://api.tnz.co.nz/...

# โ”€โ”€ NigerianBulkSMS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
NIGERIANBULKSMS_USERNAME=your_username
NIGERIANBULKSM_PASSWORD=your_password
NIGERIANBULKSMS_URL=https://portal.nigeriabulksms.com/api/

# โ”€โ”€ Montnets โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
MONTNETS_SMS_URL=your_url
MONTNETS_SMS_USERNAME=your_username
MONTNETS_SMS_PASSWORD=your_password

# โ”€โ”€ Wirepick โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
WIREPICK_SMS_URL=your_url
WIREPICK_SMS_CLIENT=your_client
WIREPICK_SMS_PASSWORD=your_password
WIREPICK_SMS_AFFLIATE=your_affliate

# โ”€โ”€ Textng.xyz (Voice) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
TEXTNGXYZ_API_KEY=your_api_key

# โ”€โ”€ KudiSMS (WhatsApp) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
KUDISMS_API_KEY=your_api_key
KUDISMS_URL=your_url

The Config File

You can publish and customize the full config at config/swissecho.php. The most important sections are:

Key Purpose
live true = send real messages; false = mock mode
sender Default sender ID/name
fake Mock strategy: "log" or "mail"
route Default channel: sms, voice, whatsapp, or slack
routes_options Per-channel gateway definitions and geo-routing rules

Geo-Routing with places

Each route (SMS, voice, WhatsApp) has a places map that automatically picks the right gateway based on the recipient's country:

'sms' => [
    'gateway_options' => [ /* ... */ ],
    'places' => [
        'nga' => [                          // Nigeria
            'gateway'   => 'nigerianbulksms',
            'phonecode' => '234'
        ],
        'gha' => [                          // Ghana
            'gateway'   => 'wirepick',
            'phonecode' => '233'
        ],
        'aus' => [                          // Australia
            'gateway'   => 'smsbroadcast',
            'phonecode' => '61'
        ],
        'nzl' => [                          // New Zealand
            'gateway'   => 'tnz',
            'phonecode' => '64'
        ],
    ]
],

The phone code is automatically prepended to phone numbers (stripping leading 0 or +).

๐Ÿ”Œ Adding a Custom SMS Gateway

Swissecho is designed to be easily extensible. If you need a gateway that isn't built in, you can wire up your own in three steps โ€” no need to touch the package source at all.

Step 1: Create Your Gateway Class

Create a folder anywhere in your project (e.g., app/Sms/Gateways/MyProvider/) and add a class inside it. The class must:

  • Extend Tekkenking\Swissecho\Routes\Sms\Gateways\BaseGateway
  • Implement two methods: init() and send()
<?php

namespace App\Sms\Gateways\MyProvider;

use Tekkenking\Swissecho\Routes\Sms\Gateways\BaseGateway;

class MyProvider extends BaseGateway
{
    /**
     * Build and return the request payload that will be passed to send().
     *
     * Available properties (populated by BaseGateway from the message builder):
     *   $this->to      โ€” array of recipient phone numbers
     *   $this->sender  โ€” sender ID / name
     *   $this->body    โ€” the SMS message text
     *   $this->config  โ€” your gateway's config block from config/swissecho.php
     *
     * @return mixed  Any value you like โ€” it will be passed directly to send().
     *                Typically a URL string, an array, or a Guzzle request object.
     */
    public function init(): mixed
    {
        // Example: build a query-string URL
        $url  = $this->config['url'];
        $url .= '?api_key='   . $this->config['auth']['api_key'];
        $url .= '&from='      . $this->sender;
        $url .= '&to='        . implode(',', $this->to);
        $url .= '&message='   . urlencode($this->body);

        return $url;
    }

    /**
     * Receive the value returned by init(), make the HTTP call,
     * and return a configured cURL handle.
     *
     * @param  mixed $data  Whatever init() returned.
     * @return \CurlHandle|bool  A ready-to-execute cURL handle.
     *                           Swissecho calls curl_exec() on it automatically.
     */
    public function send($data): \CurlHandle|bool
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // โš ๏ธ Only disable in local/testing environments; keep true in production
        return $ch;
    }
}

How it works under the hood: after send() returns the cURL handle, Swissecho's SwissechoGatewayTrait::execCurl() calls curl_exec(), collects the response, formats it, and fires the AfterSend event.

Step 2: Register the Gateway in the Config

Open config/swissecho.php and add your gateway to the routes_options.sms.gateway_options array:

use App\Sms\Gateways\MyProvider\MyProvider; // โ† import your class

'routes_options' => [
    'sms' => [
        'gateway_options' => [

            // ... existing gateways ...

            'myprovider' => [               // โ† the key becomes the gateway "name"
                'class'  => MyProvider::class,
                'url'    => env('MYPROVIDER_URL'),
                'auth'   => [
                    'api_key' => env('MYPROVIDER_API_KEY'),
                ],
                // add any other keys your gateway needs โ€” they will all be
                // available inside your class as $this->config['...']
            ],
        ],
    ],
],

Add the corresponding values to your .env:

MYPROVIDER_URL=https://api.myprovider.com/send
MYPROVIDER_API_KEY=your_api_key_here

Step 3: Use Your Gateway

That's it โ€” your gateway is now a first-class citizen in Swissecho. Use it exactly like any built-in gateway:

// Direct / fluent sending
swissecho()->gateway('myprovider')->quick('2348012345678', 'Hello!');

// Or inside a route callback
swissecho()->route('sms', function ($ms) {
    return $ms->to('2348012345678')
              ->content('Your OTP is 9988')
              ->gateway('myprovider');
})->go();

// Or inside a Laravel Notification
public function toSms($notifiable): SwissechoMessage
{
    return (new SwissechoMessage())
        ->line('Your order has shipped!')
        ->gateway('myprovider');
}

You can also map it to a country in places for automatic geo-routing:

'places' => [
    'zaf' => [                  // South Africa, for example
        'gateway'   => 'myprovider',
        'phonecode' => '27',
    ],
],

BaseGateway Quick Reference

Member Type Description
$this->to array Recipient phone numbers
$this->sender string Sender ID / name
$this->body string The message text
$this->config array Your gateway's full config block from swissecho.php
init(): mixed abstract method Build the request payload; return value is passed to send()
`send($data): \CurlHandle bool` abstract method

Usage

Swissecho can be used in two ways: directly (without a Notification class), or through Laravel's notification system.

Access Methods

You have three ways to get a Swissecho instance:

// 1. Global helper function
swissecho()

// 2. Laravel Facade
Swissecho::

// 3. From the container
app('swissecho')

A) Direct Sending (Without Notification Classes)

Quick Send โ€” One Liner

The simplest way to send a message. Uses the default route and default gateway from config:

swissecho()->quick('2348012345678', 'Your OTP code is 1234');

Quick Send with a Specific Gateway

swissecho()->gateway('vonage')->quick('2348012345678', 'Your OTP code is 1234');

Fluent Builder โ€” Full Control

swissecho()->route('sms', function ($ms) {
    return $ms->to('2348012345678, 2348098765432')  // comma-separated recipients
              ->content('Your order has been shipped!')
              ->line('Track it at https://example.com/track');  // appends a new line
              // ->gateway('routemobile')   // override gateway inside callback
              // ->sender('MyBrand')        // override sender inside callback
})
->to('2348011111111')       // optional: additional/fallback recipient
->sender('MyApp')           // optional: override sender
->gateway('termii')         // optional: override gateway
->go();                     // ๐Ÿš€ sends the message

Property-Based Sending

You can also set properties directly on the Swissecho instance:

$sw = swissecho();
$sw->gateway('termii');
$sw->to = '2348012345678';
$sw->sender = 'MyApp';
$sw->message = 'The world is a beautiful place created by GOD';
$sw->go();

Sending via WhatsApp

swissecho()->route('whatsapp', function ($ms) {
    return $ms->to('2348012345678')
              ->content('Hello from WhatsApp!');
})->go();

Sending via Slack

swissecho()->message('Hello team!')
    ->to('CHANNEL_ID')
    ->route('slack')
    ->go();

Sending via Voice Call

swissecho()->route('voice', function ($ms) {
    return $ms->to('2348012345678')
              ->content('Your OTP is 5 6 7 8');
})->gateway('termii')->go();

B) Laravel Notification Channel Integration

Swissecho integrates with Laravel's built-in notification system. Create a notification class and define a toSms (or toVoice, toWhatsapp, toSlack) method:

Step 1: Create the Notification

<?php

namespace App\Notifications;

use Illuminate\Notifications\Notification;
use Tekkenking\Swissecho\SwissechoMessage;

class OrderShipped extends Notification
{
    /**
     * The delivery channels.
     */
    public function via($notifiable): array
    {
        return ['swissecho'];
    }

    /**
     * (Optional) Tell Swissecho which routes to use.
     * If omitted, defaults to ['sms', 'slack', 'whatsapp'].
     */
    public function swissechoRoutes($notifiable): array
    {
        return ['sms'];
    }

    /**
     * Build the SMS message.
     * Method name follows the pattern: to{Route}  โ†’  toSms, toVoice, toWhatsapp, toSlack
     */
    public function toSms($notifiable): SwissechoMessage
    {
        return (new SwissechoMessage())
            ->line('Hi ' . $notifiable->name . '!')
            ->line('Your order has been shipped.')
            ->sender('MyStore');
    }
}

Step 2: Make Your User Model "Notifiable"

Swissecho pulls the phone number from the notifiable model. Implement one of these:

class User extends Authenticatable
{
    use Notifiable;

    /**
     * Option A: Have a `phone` attribute on the model (e.g. column in DB).
     * Swissecho checks $notifiable->phone automatically.
     */

    /**
     * Option B: Define this method for custom logic.
     */
    public function routeNotificationPhone(): string
    {
        return $this->mobile_number;
    }

    /**
     * (Optional) Tell Swissecho the recipient's country for geo-routing.
     * Return a 3-letter ISO code matching a key in config places.
     */
    public function routeNotificationPlace(): string
    {
        return 'nga'; // Nigeria
    }
}

Step 3: Send the Notification

$user->notify(new OrderShipped());

SwissechoMessage API Reference

The SwissechoMessage class is the message builder used in callbacks and notification methods:

Method Description Example
->line($text) Appends a line of text to the message body. Multiple calls add new lines. ->line('Hello')
->content($text) Alias for line(). ->content('Hello')
->to($recipient) Sets the recipient(s). Accepts a string or comma-separated list. ->to('234801..., 234809...')
->sender($name) Sets the sender ID (max 10 characters). ->sender('MyApp')
->from($name) Alias for sender(). ->from('MyApp')
->gateway($name) Overrides the gateway for this message. ->gateway('termii')
->place($code) Sets the country/place code (e.g., 'nga'). Overrides auto-detection. ->place('nga')
->phonecode($code) Manually sets the phone country code (e.g., '234'). ->phonecode('234')
->identifier($id) Attaches an identifier (e.g., user ID) to the message for tracking. ->identifier($user->id)
->route($name) Sets the route/channel on the message itself. ->route('sms')

Mock Mode (Development & Testing)

When SWISSECHO_ENABLED=false (the default), no real API calls are made. Instead, messages are captured by the mock system.

Mock via Log (Default)

Messages are written to storage/logs/swissecho_mock.log:

SWISSECHO_FAKE=log

The log includes: sender, recipient, message body, route, gateway, gateway class, country, and phone code.

Mock via Email

Messages are emailed to the configured address:

SWISSECHO_FAKE=mail
SWISSECHO_FAKE_MAIL=developer@example.com

Events

After every message send (including mock sends), Swissecho dispatches the AfterSend event:

Tekkenking\Swissecho\Events\AfterSend

Event Properties

Property Type Description
$insightPayload array Contains request (the payload sent to the gateway) and response (raw gateway response)
$formattedResponse array Structured response with status, partner_response, from, to, body, route, gateway, identifier, timestamp
$identifier mixed The identifier attached to the message (e.g., user ID)

Listening to the Event

// app/Providers/EventServiceProvider.php
protected $listen = [
    \Tekkenking\Swissecho\Events\AfterSend::class => [
        \App\Listeners\LogSmsDelivery::class,
    ],
];
<?php
// app/Listeners/LogSmsDelivery.php

namespace App\Listeners;

use Tekkenking\Swissecho\Events\AfterSend;

class LogSmsDelivery
{
    public function handle(AfterSend $event): void
    {
        // $event->formattedResponse['status']   โ€” true/false
        // $event->formattedResponse['to']       โ€” recipient array
        // $event->formattedResponse['gateway']  โ€” which gateway was used
        // $event->insightPayload['request']     โ€” raw request payload
        // $event->insightPayload['response']    โ€” raw response from provider

        logger()->info('SMS sent', $event->formattedResponse);
    }
}

Webhooks

Swissecho includes a built-in webhook handler for receiving delivery reports or callbacks from gateway providers. The webhook system validates a secret key and routes the request to the appropriate gateway handler.

Configure webhooks per gateway in config/swissecho.php:

'{$gateway}' => [
    // ...
    'webhook' => [
        'secret' => env('TERMI_WEBHOOK_SECRET', 'your-secret-here'),
        'handle' => 'webhook'   // method name on the gateway class
    ]
],

Helper Functions

Swissecho provides global helper functions for phone number manipulation:

Function Description
swissecho() Returns the Swissecho singleton instance
addCountryCodeToPhoneNumber($phone, $code) Prepends a country code (e.g., '234') to a phone number, stripping leading 0 or +
removeCountryCodeFromPhoneNumber($phone, $code) Strips a country code prefix from a phone number
convertPhoneNumberToArray($phone) Splits a comma-separated phone string into an array

Examples

addCountryCodeToPhoneNumber('08012345678', '234');
// Returns: "2348012345678"

removeCountryCodeFromPhoneNumber('2348012345678', '234');
// Returns: "8012345678"

convertPhoneNumberToArray('2348012345678, 2348098765432');
// Returns: ["2348012345678", "2348098765432"]

License

This package is open-sourced software licensed under the MIT license.