laravel-smsplanet maintained by beziworld
Laravel SMSPlanet
Laravel integration for SMSPlanet: SMS and MMS sending, personalised bulk messages, a notification channel, signed delivery webhooks, incoming two-way messages and multiple accounts.
Features
- Fluent
SmsMessage(up to 10 000 recipients, personalisation with[%parametr1%]..[%parametr4%]) andMmsMessage - Scheduling, sender names, test mode,
clear_polish, transactional channel, dispatch name - Polish numbers normalised to
+48...,SmsCounterhelper for GSM-7 / UCS-2 parts (andpartsCount()via the API) smsplanetnotification channel, including on-demand notifications and per-message account selection- Delivery webhook (
MESSAGE_NOTIFICATION_WEBHOOK) with HMAC-SHA256 signature verification and a Laravel event - Incoming messages of two-way numbers forwarded to a token-protected endpoint
- Dispatch details, balance, sender names, blacklist, contacts, groups, link shortener and webhook management
- Multiple accounts:
SmsPlanet::connection('name')andSmsPlanet::using([...])for tenant credentials - One exception hierarchy exposing the SMSPlanet error code
- PHP 8.3+, Laravel 12 and 13, PHPStan level 8
Installation
composer require beziworld/laravel-smsplanet
php artisan vendor:publish --tag=smsplanet-config # optional
SMSPLANET_TOKEN=your-api-token # generated in the customer panel
# or the API key and password instead of the token:
# SMSPLANET_KEY=your-api-key
# SMSPLANET_PASSWORD=your-api-password
SMSPLANET_FROM=YourBrand # approved sender name; "TEST" works for testing
SMSPLANET_TEST_MODE=false # true: validate requests without sending
Sending messages
use Beziworld\SmsPlanet\Facades\SmsPlanet;
use Beziworld\SmsPlanet\Messages\SmsMessage;
// Shortcut
SmsPlanet::sendText('600 111 222', 'Your code is 1234');
// Full builder
$result = SmsPlanet::send(
SmsMessage::create('Your order #1001 has shipped')
->to(['600111222', '+48700333444'])
->from('Shop')
->sendAt(now()->addHour())
->name('Order 1001') // visible in the panel history
->clearPolish() // ą to a, ł to l, ... (160 characters per part)
);
$result->messageId; // id of the dispatch
$result->isBuffered(); // "B-..." ids are queued for 10-30 s and cannot be cancelled
// Personalised: one request, up to 100 recipients
SmsPlanet::send(
SmsMessage::create('Hi [%parametr1%], your visit is at [%parametr2%].')
->toPersonalized('600111222', 'Jan', '10:00')
->toPersonalized('700333444', 'Ewa', '11:30')
);
Other options: test(), transactional() (one-time codes, must be enabled for the account), companyId().
use Beziworld\SmsPlanet\Messages\MmsMessage;
SmsPlanet::sendMms(
MmsMessage::create('Promo') // subject
->from('48664079876') // MMS number shown in the panel
->to('600111222')
->content('-20% today')
->attachment('https://example.com/promo.jpg') // URL or Base64, 300 kB in total
);
SmsPlanet::cancel($result->messageId);
Account and data
SmsPlanet::messageInfo($messageId); // MessageInfo with per-recipient delivery (max once per 3 minutes)
SmsPlanet::balance(); // PrePaid points
SmsPlanet::partsCount($text); // parts as calculated by SMSPlanet
SmsPlanet::senderFields('SMS'); // ['TEST', 'Shop', ...]
SmsPlanet::addSenderField('MY BRAND');
SmsPlanet::addToBlacklist('600111222', now()->addYear()); SmsPlanet::removeFromBlacklist('600111222');
SmsPlanet::findContacts(msisdn: '600111222'); SmsPlanet::saveContact(['msisdn' => '600111222', 'name' => 'Jan'], ['Clients']);
SmsPlanet::groups(); SmsPlanet::saveGroup('VIP'); SmsPlanet::addContactToGroup($contactId, $groupId);
SmsPlanet::shortenUrl('https://example.com/offer');
SmsPlanet::post('/generateReport', ['from' => '01-05-2026 00:00:00', 'to' => '31-05-2026 23:59:59']); // any other endpoint
Notification channel
use Beziworld\SmsPlanet\Messages\SmsMessage;
use Illuminate\Notifications\Notification;
class AppointmentReminder extends Notification
{
public function via(object $notifiable): array
{
return ['smsplanet'];
}
public function toSmsPlanet(object $notifiable): SmsMessage|string
{
return SmsMessage::create("Reminder: your visit on {$this->visit->starts_at->format('d.m H:i')}.");
}
}
The recipient is taken from the notifiable:
public function routeNotificationForSmsplanet(): ?string
{
return $this->phone;
}
On-demand: Notification::route('smsplanet', '600111222')->notify(new AppointmentReminder($visit));
A message may define its own recipients with ->to() and its account with ->connection('clinic'). A notifiable
without a phone number is skipped silently.
Delivery reports (webhook)
-
Enable the endpoint and set the Signature Key from the panel (API > Webhooki):
SMSPLANET_WEBHOOK_ENABLED=true SMSPLANET_SIGNATURE_KEY=key-from-the-panel -
Register the webhook once (for example in a deploy script or Tinker):
SmsPlanet::createWebhook(route('smsplanet.webhook')); -
Listen to the event:
use Beziworld\SmsPlanet\Events\DeliveryReportReceived; Event::listen(function (DeliveryReportReceived $event) { $report = $event->report; $report->messageId; // id of the dispatch $report->to; // recipient $report->isDelivered(); $report->deliveredAt; $report->error(); // DeliveryError enum when the error is a documented status code });
Every request must carry a valid Signature header (Base64 HMAC-SHA256 of the raw body); requests without a valid
signature, or any request when no key is configured, are rejected with 403. With several accounts each connection
may define its own signature_key; the matching connection is exposed as $event->report->connection. SMSPlanet
retries a notification for about 16 hours until it receives HTTP 200, so keep listeners idempotent.
Incoming messages (two-way numbers)
SMSPLANET_INCOMING_ENABLED=true
SMSPLANET_CALLBACK_TOKEN=long-random-string
SMSPLANET_CALLBACK_IPS= # optional allow-list
Set the forwarding URL in the panel (Odbieranie wiadomości > Ustawienia) to
https://your-app.com/smsplanet/incoming?token=long-random-string and listen to IncomingMessageReceived.
SMSPlanet does not document the parameters of the forwarding, so the endpoint reads the common names (from,
msisdn, phone for the sender, msg, message, text for the content, to, date) and keeps every parameter
in $event->message->parameters.
Multiple accounts
// config/smsplanet.php
'default' => env('SMSPLANET_CONNECTION', 'default'),
'connections' => [
'default' => ['token' => env('SMSPLANET_TOKEN'), 'from' => env('SMSPLANET_FROM'), 'signature_key' => env('SMSPLANET_SIGNATURE_KEY')],
'clinic' => ['token' => env('SMSPLANET_CLINIC_TOKEN'), 'from' => 'Clinic', 'signature_key' => env('SMSPLANET_CLINIC_SIGNATURE_KEY')],
],
SmsPlanet::connection('clinic')->sendText($phone, 'Hi');
// Credentials stored per tenant
SmsPlanet::using(['token' => $tenant->smsplanet_token, 'from' => $tenant->sms_sender])->send($message);
Error handling
All exceptions extend Beziworld\SmsPlanet\Exceptions\SmsPlanetException:
| Exception | When |
|---|---|
ApiException |
SMSPlanet returned an error (errorCode(), httpStatus(), payload(), isAuthenticationError()) or could not be reached |
InvalidMessage |
Message without recipients, content, sender or attachment, inconsistent personalisation |
InvalidConfiguration |
Missing credentials or unknown connection |
CouldNotSendNotification |
Notification without a valid toSmsPlanet() |
SMSPlanet answers errors with HTTP 200 and {"errorMsg", "errorCode"}, for example 101 invalid key, 103 invalid
sender, 109 insufficient funds, 201 invalid token. Some endpoints return only errorMsg (then errorCode() is
null).
try {
SmsPlanet::sendText($phone, $text);
} catch (ApiException $e) {
if ($e->errorCode() === 109) {
// top up the account
}
}
Limits and retries
SMSPlanet accepts 1000 send requests and 300 other requests per minute and blocks the API for 60 seconds when the
limit is exceeded; send many recipients in one request instead of one request per number. Retries are disabled by
default (SMSPLANET_RETRIES=0) because a timed-out request may still have been accepted.
Testing your application
Http::fake(['api2.smsplanet.pl/*' => Http::response(['messageId' => '191919'])]);
Development
composer install
composer ci # pint --test, phpstan (level 8), pest
Author
Created and maintained by Kamil Ćwiertnia, beziworld.eu.
- E-mail: biuro@beziworld.eu
- Issues and feature requests: GitHub issues
- Security vulnerabilities: see SECURITY.md
Need a custom integration, an SMS gateway setup or a Laravel project? Get in touch through beziworld.eu.
License
MIT. See LICENSE.md. This package is not affiliated with SMSPlanet.