smsapi.pl/php-client
Official PHP client for SMSAPI.pl for sending SMS, MMS and VMS messages, managing contacts, groups, templates and sender names, with bulk sending, delivery reports and easy Laravel integration.
What you're working with. smsapi/php-client is a plain PHP SDK for the SMSAPI gateway (smsapi.pl — predominantly Polish/CEE market: SMS, MMS, VMS, contacts, sender names, blacklists, templates). It is not a Laravel package: no service provider, no facade, no config file, no auto-discovery, no Sms::fake(). You get a thin, well-typed wrapper around the HTTP API and you own everything above it. With 0 dependents and ~66 stars, treat it as a vendor SDK you wrap, not a framework citizen.
Minimal steps.
composer require smsapi/php-clienttests/ directory — the tests are the most accurate documentation of bag/feature construction and of the exact payloads sent to the gateway.from) there too — you cannot invent one at runtime.SmsapiService bound as a singleton in AppServiceProvider, reading config from config/smsapi.php (which in turn reads env() — never call env() outside config files).Sketch of the bootstrap (verify exact factory names against the installed version's README; the SmsapiClient / Feature / Bag shape is stable, the constructor sugar is not):
// config/smsapi.php
return [
'token' => env('SMSAPI_TOKEN'),
'sender' => env('SMSAPI_SENDER', 'MyApp'),
'format' => env('SMSAPI_FORMAT', 'hybrid'), // sms | unicode | hybrid
'timeout' => 5,
];
// AppServiceProvider::register()
$this->app->singleton(SmsapiClient::class, fn () => SmsapiClient::withBearerToken(config('smsapi.token')));
$this->app->bind(SmsTransport::class, SmsapiTransport::class);
First use case that pays for itself: transactional SMS — OTP on password reset, order-confirmation ping. That's one recipient, one message, and it forces you to solve the three things that actually matter: phone normalization, sender name, and character encoding.
Do not call the client from controllers. Define a SmsTransport interface (send(string $to, string $body, array $opts = []): string) and put the SDK call behind it. The payoff is a FakeSmsTransport for tests and the ability to swap providers later — with an NOASSERTION licence and a single maintainer, a cheap escape hatch is prudent.
Use Laravel notifications, not raw service calls. Create an SmsChannel (send($notifiable, Notification $n)) and have notifiables expose routeNotificationForSmsapi(). Then:
class OrderShipped extends Notification implements ShouldQueue — always queue. An HTTP round-trip to the gateway inside web is a request you'll regret at peak load.Notification::route('smsapi', $phone)->notify(...) for non-User recipients (invited users, leads).Bulk sends. The SDK exposes a bulk bag for one-message-many-recipients and a personalised variant. Chunk lists (a few thousand per call, not the full table) and set your own idx per message so delivery reports are correlatable. For personalisation, pass per-recipient params rather than looping send() — one HTTP call beats 5,000 and avoids hammering rate limits.
Persistence and delivery reports. Store an sms_messages row (idx, to, status, segments, cost, provider_response) before dispatching. Configure the callback URL in the panel and add a route:
updateOrCreate on idx) because retries happen.Scheduled/campaign traffic. A queued job with ->delay() plus RateLimiter/Bus::batch() gives you pacing and per-batch failure reporting. The SDK has no throttling of its own.
Testing. Point the container at a fake in TestCase::setUp(). For integration-level tests, the SDK rides on Guzzle, so you can inject a mock handler with canned JSON responses and assert the outgoing payload — worth doing once to lock down encoding and from behaviour.
Multi-account. Marketing traffic and transactional traffic usually live on separate SMSAPI subaccounts (different sender names, separate limits and billing). Bind two singletons with distinct config keys rather than one global client.
from returns a per-message error, not a client exception. Validate it once at boot (or in a health check) instead of discovering it in production.48XXXXXXXXX), not +48 … and not local 500 600 700. Normalise and validate in a form request or a mutator (propaganistas/laravel-phone is a good fit) and store canonical numbers — a rejected number costs you a failed OTP and a support ticket.sms = GSM-7 (160 chars), unicode = UCS-2 (70 chars/segment), hybrid = auto-detect per message. One emoji or a stray ą flips a message to UCS-2 and triples the segment count. Choose deliberately: hybrid for safety, sms + normalize (strip diacritics) when cost matters and legibility survives without Polish characters.idx. Classify transient (5xx, timeout, 429) vs permanent (invalid number, no funds) before deciding to retry, and let Laravel's $this->release($backoff) or Retry middleware handle the transient case.config:cache traps. Any env() call inside your transport class resolves to null after php artisan config:cache. Keep the env reads strictly in config/smsapi.php.48*****6700) and the idx, never the body — OTPs in logs are a breach waiting to happen.hash_equals.Bag objects are mutable builders; build a fresh one per send rather than reusing a mutated bag across a loop, which is a classic source of "everyone got the first person's name".composer.json, and keep the SDK call confined to one class so a breaking change or an abandoned release is a one-file fix.How can I help you explore Laravel packages today?