Looking to hire Laravel developers? Try LaraJobs
This package is not available.

laravel-serwersms maintained by beziworld

Description
Laravel integration for SerwerSMS.pl: SMS, MMS and voice sending, notification channel, delivery reports, HLR and multiple accounts.
Last update
2026/11/04 17:22 (dev-master)
License
Downloads
1

Comments
comments powered by Disqus

Laravel SerwerSMS

tests Latest Version License

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.

Polska wersja dokumentacji

Features

  • Fluent builders: SmsMessage, PersonalizedMessage, VoiceMessage, MmsMessage
  • Scheduling, sender names (SMS FULL) or SMS ECO, unique_id tracking, flash, priority channel, validity, test mode
  • Automatic Unicode (utf) for texts with Polish characters, SmsCounter helper for GSM-7 / UCS-2 parts
  • Polish numbers normalised to +48... (500 600 700, 48500600700, 0048...)
  • serwersms notification 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') and SerwerSms::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

  1. 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)
    
  2. 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 the items format of the API are supported too.

  3. 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.

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.