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.
| 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 messaging | KudiSMS | |
| Slack | Slack notifications | Incoming Webhook, Bot Token (chat.postMessage) |
| Telegram | Telegram Bot messages | Telegram Bot API (sendMessage) |
- π 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
AfterSendevent 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)
| Dependency | Supported Versions |
|---|---|
| PHP | ^8.1 |
| Laravel | 11.x, 12.x, 13.x |
composer require tekkenking/swissechoThe package auto-discovers itself via Laravel's package auto-discovery β no manual registration needed.
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
# ββ Slack βββββββββββββββββββββββββββββββββββββββββββββββββ
# Option A β Incoming Webhook (no channel needed in code)
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T.../B.../...
# Option B β Bot Token (requires a channel ID or name)
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_DEFAULT_CHANNEL=#general
# SLACK_API_URL defaults to https://slack.com/api/chat.postMessage
# ββ Telegram ββββββββββββββββββββββββββββββββββββββββββββββ
TELEGRAM_BOT_TOKEN=123456:ABC-your-telegram-bot-token
TELEGRAM_DEFAULT_CHAT_ID=123456789
# TELEGRAM_PARSE_MODE=HTML # Optional: HTML or MarkdownV2
# TELEGRAM_API_URL= # Optional: override the API URLYou 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, slack, or telegram |
routes_options |
Per-channel gateway definitions and geo-routing rules |
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 +).
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.
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()andsend()
<?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'sSwissechoGatewayTrait::execCurl()callscurl_exec(), collects the response, formats it, and fires theAfterSendevent.
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_hereThat'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',
],
],| 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 |
Swissecho can be used in two ways: directly (without a Notification class), or through Laravel's notification system.
You have three ways to get a Swissecho instance:
// 1. Global helper function
swissecho()
// 2. Laravel Facade
Swissecho::
// 3. From the container
app('swissecho')The simplest way to send a message. Uses the default route and default gateway from config:
swissecho()->quick('2348012345678', 'Your OTP code is 1234');swissecho()->gateway('vonage')->quick('2348012345678', 'Your OTP code is 1234');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 messageYou 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();swissecho()->route('whatsapp', function ($ms) {
return $ms->to('2348012345678')
->content('Hello from WhatsApp!');
})->go();Incoming Webhook mode (set SLACK_WEBHOOK_URL β no recipient needed):
swissecho()->message('Deployment finished β
')
->route('slack')
->go();Bot Token mode (set SLACK_BOT_TOKEN β channel is required):
swissecho()->message('Hello team!')
->to('#alerts')
->route('slack')
->go();swissecho()->message('Server restarted π')
->to('123456789') // your Telegram chat_id
->route('telegram')
->go();swissecho()->route('voice', function ($ms) {
return $ms->to('2348012345678')
->content('Your OTP is 5 6 7 8');
})->gateway('termii')->go();Swissecho integrates with Laravel's built-in notification system. Create a notification class and define a toSms (or toVoice, toWhatsapp, toSlack) method:
<?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', 'telegram'].
*/
public function swissechoRoutes($notifiable): array
{
return ['sms'];
}
/**
* Build the SMS message.
* Method name follows the pattern: to{Route} β toSms, toVoice, toWhatsapp, toSlack, toTelegram
*/
public function toSms($notifiable): SwissechoMessage
{
return (new SwissechoMessage())
->line('Hi ' . $notifiable->name . '!')
->line('Your order has been shipped.')
->sender('MyStore');
}
}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) Slack channel for this user.
*/
public function routeNotificationSlackChannel(): string
{
return '#team-alerts';
}
/**
* (Optional) Telegram chat_id for this user.
*/
public function routeNotificationTelegramChatId(): string
{
return $this->telegram_chat_id;
}
/**
* (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
}
}$user->notify(new OrderShipped());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') |
When SWISSECHO_ENABLED=false (the default), no real API calls are made. Instead, messages are captured by the mock system.
Messages are written to storage/logs/swissecho_mock.log:
SWISSECHO_FAKE=logThe log includes: sender, recipient, message body, route, gateway, gateway class, country, and phone code.
Messages are emailed to the configured address:
SWISSECHO_FAKE=mail
SWISSECHO_FAKE_MAIL=developer@example.comAfter every message send (including mock sends), Swissecho dispatches the AfterSend event:
Tekkenking\Swissecho\Events\AfterSend
| 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) |
// 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);
}
}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
]
],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 |
addCountryCodeToPhoneNumber('08012345678', '234');
// Returns: "2348012345678"
removeCountryCodeFromPhoneNumber('2348012345678', '234');
// Returns: "8012345678"
convertPhoneNumberToArray('2348012345678, 2348098765432');
// Returns: ["2348012345678", "2348098765432"]This package is open-sourced software licensed under the MIT license.