laravel-serwersms maintained by beziworld
Laravel SerwerSMS
Laravel integration for SerwerSMS.pl (HTTPS API v2): SMS ECO and FULL, personalised, voice and MMS messages, a notification channel, delivery report and incoming message webhooks, HLR checks and multiple accounts.
Features
- Fluent builders:
SmsMessage,PersonalizedMessage,VoiceMessage,MmsMessage - Scheduling, sender names (SMS FULL) or SMS ECO,
unique_idtracking, flash, priority channel, validity, test mode - Automatic Unicode (
utf) for texts with Polish characters,SmsCounterhelper for GSM-7 / UCS-2 parts - Polish numbers normalised to
+48...(500 600 700,48500600700,0048...) serwersmsnotification channel, including on-demand notifications and per-message account selection- Delivery report and incoming message endpoints dispatching Laravel events
- Reports, incoming messages, HLR check and network lookup, account limits, sender names, blacklist, groups, contacts and templates
- Multiple accounts:
SerwerSms::connection('name')andSerwerSms::using([...])for tenant credentials - Typed responses and one exception hierarchy exposing the SerwerSMS.pl error code
- PHP 8.3+, Laravel 12 and 13, PHPStan level 8
Installation
composer require beziworld/laravel-serwersms
php artisan vendor:publish --tag=serwersms-config # optional
SERWERSMS_TOKEN=your-api-token # panel: Ustawienia interfejsów > HTTPS API > Tokeny API
# or an API user instead of the token:
# SERWERSMS_USERNAME=webapi_user
# SERWERSMS_PASSWORD=secret
SERWERSMS_SENDER=YourBrand # approved sender name; empty = SMS ECO
SERWERSMS_TEST_MODE=false # true: validate requests without sending
Sending messages
use Beziworld\SerwerSms\Facades\SerwerSms;
use Beziworld\SerwerSms\Messages\SmsMessage;
// Shortcut
SerwerSms::sendText('500 600 700', 'Your code is 1234');
// Full builder
$result = SerwerSms::send(
SmsMessage::create('Your order #1001 has shipped')
->to(['500600700', '+48600700800'])
->from('Shop') // SMS FULL; ->eco() sends SMS ECO
->uniqueId('order1001a', 'order1001b') // one per recipient, returned in reports
->sendAt(now()->addHour())
);
$result->queued; // number of queued messages
$result->ids(); // SerwerSMS ids of the queued messages
$result->failedMessages(); // recipients rejected individually, with errorCode / errorMessage
Other options: toGroup(), toContact(), utf(), flash(), speed(), validity($minutes), dlrUrl(), test().
When the message has a sender and the text contains characters outside the GSM alphabet, utf is enabled
automatically (set SERWERSMS_UTF=false to disable it).
use Beziworld\SerwerSms\Messages\{MmsMessage, PersonalizedMessage, VoiceMessage};
SerwerSms::sendPersonalized(
PersonalizedMessage::create()->add('500600700', 'Hi Jan')->add('600700800', 'Hi Ewa')
);
SerwerSms::sendVoice(VoiceMessage::create('Your visit is tomorrow at 10.')->to('500600700')->lector('Maja'));
$fileId = SerwerSms::uploadFile('mms', 'https://example.com/promo.jpg');
SerwerSms::sendMms(MmsMessage::create('Promo')->files($fileId)->text('-20% today')->to('500600700'));
SerwerSms::cancelScheduled(ids: ['111065e43c'], uniqueIds: ['order1001a']);
Segment estimate before sending: SmsCounter::parts($text) and SmsCounter::encoding($text)
(Beziworld\SerwerSms\Support\SmsCounter).
Account and data
SerwerSms::reports(['id' => [$id], 'date_from' => now()->subDay()]); // list<MessageReport>
SerwerSms::report($id); // ?MessageReport
SerwerSms::received('eco', markAsRead: true); // incoming messages
SerwerSms::checkNumber('500600700'); // HLR: availability, network, porting (billed)
SerwerSms::lookupNumber('500600700'); // network lookup (billed)
SerwerSms::limits()->get('full')?->remaining(); // remaining messages per type
SerwerSms::senders(); SerwerSms::addSender('NewBrand');
SerwerSms::blacklist(); SerwerSms::addToBlacklist($phone); SerwerSms::isBlacklisted($phone);
SerwerSms::groups(); SerwerSms::contacts($groupId); SerwerSms::createContact($groupId, $phone);
SerwerSms::templates();
SerwerSms::call('stats/index', [...]); // any other HTTPS API v2 action
Notification channel
use Beziworld\SerwerSms\Messages\SmsMessage;
use Illuminate\Notifications\Notification;
class OrderShipped extends Notification
{
public function via(object $notifiable): array
{
return ['serwersms'];
}
public function toSerwerSms(object $notifiable): SmsMessage|string
{
return SmsMessage::create("Order {$this->order->number} has shipped");
}
}
The recipient is taken from the notifiable:
public function routeNotificationForSerwersms(): ?string
{
return $this->phone;
}
On-demand: Notification::route('serwersms', '500600700')->notify(new OrderShipped($order));
A message may define its own recipients with ->to() and its account with ->connection('shop'). A notifiable
without a phone number is skipped silently.
Delivery reports and incoming messages
-
Enable the endpoints and choose a secret token:
SERWERSMS_DELIVERY_REPORTS_ENABLED=true SERWERSMS_INCOMING_ENABLED=true SERWERSMS_CALLBACK_TOKEN=long-random-string SERWERSMS_CALLBACK_IPS= # optional allow-list (IPs or CIDR ranges) -
In the SerwerSMS.pl panel (Ustawienia interfejsów > HTTPS XML API > Raporty doręczeń) set the delivery report URL template (or send it with every message through
SERWERSMS_DLR_URL/dlrUrl()):https://your-app.com/serwersms/delivery-report?token=long-random-string&id=#SMSID#&status=#STAN#&date=#DATA#&reason=#PRZYCZYNA#Forward incoming messages (SMS ECO replies, ND/NDI) to
https://your-app.com/serwersms/incoming?token=long-random-string&phone=...&text=...using the placeholders offered by the panel for the sender number, text and date. MMS posted in theitemsformat of the API are supported too. -
Listen to the events:
use Beziworld\SerwerSms\Events\DeliveryReportReceived; use Beziworld\SerwerSms\Events\IncomingMessageReceived; Event::listen(function (DeliveryReportReceived $event) { $event->report->messageId; // #SMSID# $event->report->status; // DeliveryStatus::Delivered, Undelivered, Unsent, Pending $event->report->reason; // #PRZYCZYNA# }); Event::listen(function (IncomingMessageReceived $event) { $event->message->phone; // +48500600700 $event->message->text; });
Both endpoints answer OK, which stops the retries of SerwerSMS.pl (5, 15, 60 minutes and 24 hours). SerwerSMS.pl
does not sign callbacks, hence the token: without a configured token every request is rejected. The token travels
in the query string and appears in access logs, so use a long random value and the IP allow-list. Add
&connection=shop to the URL in multi-account setups; it is exposed as $event->report->connection. Keep listeners
idempotent.
Multiple accounts
// config/serwersms.php
'default' => env('SERWERSMS_CONNECTION', 'default'),
'connections' => [
'default' => ['token' => env('SERWERSMS_TOKEN'), 'sender' => env('SERWERSMS_SENDER')],
'shop' => ['token' => env('SERWERSMS_SHOP_TOKEN'), 'sender' => 'Shop'],
],
SerwerSms::connection('shop')->sendText($phone, 'Hi');
// Credentials stored per tenant
SerwerSms::using(['token' => $tenant->serwersms_token, 'sender' => $tenant->sms_sender])->send($message);
Options outside connections (test_mode, utf, dlr_url, http, ...) are shared and may be overridden per
connection.
Error handling
All exceptions extend Beziworld\SerwerSms\Exceptions\SerwerSmsException:
| Exception | When |
|---|---|
ApiException |
SerwerSMS.pl returned an error (errorCode(), errorType(), httpStatus(), payload()) or could not be reached |
InvalidMessage |
Message without recipients or content, invalid unique_id |
InvalidConfiguration |
Missing credentials or unknown connection |
CouldNotSendNotification |
Notification without a valid toSerwerSms() |
SerwerSMS.pl reports errors with HTTP 200 and an error object, for example 1001 InvalidUser, 1012 InvalidToken,
3002 (SMS FULL limit exhausted) or 3106 (sender name not allowed). isAuthenticationError() covers the
credential errors.
try {
SerwerSms::sendText($phone, $text);
} catch (ApiException $e) {
if ($e->errorCode() === 3002) {
// SMS FULL limit exhausted
}
}
Retries
Retries are disabled by default (SERWERSMS_RETRIES=0): a request that timed out may still have been accepted, and
repeating it would send the SMS twice. When enabling retries, give every message a uniqueId() and enable duplicate
control of unique_id in the panel.
Testing your application
Http::fake(['api2.serwersms.pl/*' => Http::response(['success' => true, 'queued' => 1, 'unsent' => 0])]);
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 SerwerSMS.pl.