leandro980/skebby-bundle
A Laravel/PHP bundle for integrating the Skebby SMS gateway, enabling applications to send transactional and notification text messages through Skebby with configurable credentials and a simple service wrapper.
First, the reality check: leandro980/skebby-bundle is a Symfony bundle, not a Laravel package. There is still no service provider, no façade, no config publishing, and no auto-discovery. v4.0.0 (Symfony 5.4|6.0, User API + Sms API, dependency fixes) is the first release in years, so the project is alive again — but "alive" means Symfony-targeted, not Laravel-ready. Treat it as a maintained reference implementation of the Skebby HTTP API that you wire up manually, or as source you copy.
The one thing v4.0.0 changes for you: the Symfony constraint now maps to a Laravel version.
| Laravel | Symfony components | v4.0.0 installable? |
|---|---|---|
| 9.x | 6.0 | Likely yes |
| 10.x | 6.2–6.3 | Likely yes (5.4/6.0 floor, check ceiling) |
| 11.x | 7.x | Almost certainly no |
Verify before planning anything:
composer require leandro980/skebby-bundle:^4.0 -W
composer show leandro980/skebby-bundle # read the actual requires block
If Composer refuses, read the code and decide whether to fork:
ls vendor/leandro980/skebby-bundle/src
find vendor/leandro980/skebby-bundle/src -name '*.php'
The files that matter:
src/DependencyInjection/Configuration.php — the only reliable documentation. Config keys (username, password, sender, sender_type, charset, type/method). v4 adds the User API surface, so expect new keys around authentication/session handling — re-read it, don't assume 3.x shapes.src/Resources/config/services.yml — service IDs and constructor arguments. Re-read this even if you used an earlier version: the User API split means there may now be two services (an auth/user client and an SMS client) instead of one.src/Service/* — the actual HTTP calls. v4 covers both the User API and the SMS send API, so the surface is larger; confirm method names by reading, not by guessing.Credentials: a Skebby account with SMS credit, plus username/password (or API key + access token). Put them in .env:
SKEBBY_USERNAME=
SKEBBY_PASSWORD=
SKEBBY_SENDER=MyApp
SKEBBY_METHOD=send_sms_classic
First use case: transactional SMS — an OTP on login, or an "ordine spedito" notification. Single recipient you control, so you can see exactly what the API returns. Bonus with v4: the User API means you can now check credit and obtain a session key before spending a single SMS on your smoke test.
Wire it into Laravel yourself:
// config/skebby.php
return [
'username' => env('SKEBBY_USERNAME'),
'password' => env('SKEBBY_PASSWORD'),
'sender' => env('SKEBBY_SENDER', 'MyApp'),
'sender_type' => env('SKEBBY_SENDER_TYPE', 'sender'),
'charset' => 'UTF-8',
'method' => env('SKEBBY_METHOD', 'send_sms_classic'),
];
// app/Providers/SkebbyServiceProvider.php
public function register(): void
{
$this->app->singleton(\SkebbyBundle\Service\SkebbySmsService::class, fn () => new \SkebbyBundle\Service\SkebbySmsService(
config('skebby.username'),
config('skebby.password'),
config('skebby.sender'),
config('skebby.charset'),
// match the constructor order in services.yml exactly — v4 may have changed it
));
}
Then a one-off smoke test in php artisan tinker — confirm method names against the source, since they still aren't documented for Laravel users:
// 1. auth first (User API) — confirms credentials and gives you a session key
// 2. then a single send
app(\SkebbyBundle\Service\SkebbySmsService::class)
->sendSms(['393331234567'], 'Test da Laravel', ['sender' => 'MyApp']);
Wrap it behind your own interface. Still the single most valuable thing you can do. The package is Symfony-shaped and its recent revival is a single release after a long silence — you want a seam.
interface SmsSender
{
public function send(string $to, string $message): void;
}
final class SkebbySender implements SmsSender { /* wraps the bundle service */ }
final class LogSmsSender implements SmsSender { /* local/testing */ }
Bind in the provider: $this->app->bind(SmsSender::class, config('sms.driver') === 'skebby' ? SkebbySender::class : LogSmsSender::class);
Exploit the User API in v4. This is the concrete day-to-day gain over the 2021 release. The User API gives you a place to:
Put that behind a SkebbyUserClient wrapper with a Cache::remember around the token.
Idiomatic Laravel: a notification channel. Day-to-day, SMS is almost never called directly from a controller:
class SmsChannel
{
public function send($notifiable, SmsNotification $notification): void
{
$message = $notification->toSms($notifiable);
SendSmsJob::dispatch($notifiable->routeNotificationFor('sms'), $message)
->onQueue('sms');
}
}
Always queue. The Skebby endpoint is a synchronous HTTP call to Italy; a 3–10 s response inside a request cycle is a timeout waiting to happen.
class SendSmsJob implements ShouldQueue
{
public int $tries = 3;
public int $backoff = [10, 60];
public function handle(SmsSender $sms): void { $sms->send($this->to, $this->message); }
}
Batch and chunk. Skebby accepts multiple recipients per request but caps them (historically 100; verify against v4 source). collect($recipients)->chunk(50), one job per chunk, so a single bad number doesn't poison a 5,000-row broadcast.
Pick the method deliberately, not by default:
send_sms_classic — cheapest, no per-recipient delivery report. Fine for OTPs and marketing.send_sms_plus — higher cost, returns delivery reports. Required if you need to prove delivery.send_sms_basic — legacy.Sender type matters more than people expect. An alphanumeric sender (MyApp) is one-way: recipients cannot reply, and some countries reject it. If you need inbound replies (e.g. "STOP" opt-outs), use a numeric sender_type and a provisioned number.
Test with a fake, never live. In phpunit.xml, set the driver to a LogSmsSender that appends to an in-memory array:
$this->assertContains('Il tuo ordine è partito', $fake->sentTo('393331234567'));
Lock the version you validated. "leandro980/skebby-bundle": "4.0.*" in composer.json. A 3.x→4.x jump after a multi-year gap means you should not assume 4.1 keeps the same Symfony range.
Composer conflicts are still the first wall — but the wall moved. v4's Symfony 5.4|6.0 floor fixes the old fight with Laravel 9/10, and simultaneously rules out Laravel 11 (Symfony 7). If you're on 11, don't fight it: copy the service code into app/Services/SkebbyClient.php and own it. That's roughly 100–200 lines and buys you the User API + SMS API without the bundle's constraints.
"Fixes dependencies" is not a changelog. The release notes tell you the Symfony range and the API coverage, and nothing else. There may be renamed methods, changed constructor argument order, or restructured services relative to earlier versions. Diff src/ against whatever you read last time rather than assuming continuity — especially services.yml and Configuration.php.
HTTP 200 does not mean sent. Skebby returns application-level errors in the JSON body ({"result": "error", "message":...} or numeric error codes) with a 200 status. Check the decoded body, not just $response->successful(). Log the raw body on every failure — this is the #1 debugging time-sink with this gateway.
Session keys expire. The User API returns a session_key/token that is time-limited. Now that v4 exposes the User API, there's no excuse for logging in on every send: cache it (Cache::put('skebby.session_key', $key, now()->addHour())) and re-authenticate only on an auth error. If the bundle's SMS service still logs in internally per call, your wrapper should intercept and reuse.
Accented characters double your cost. Italian text with è, à, ù pushes the message from GSM-7 (160 chars/segment) to UCS-2 (70 chars/segment). "Il tuo ordine è partito" is fine, but a long message with a few accents silently doubles from 1 to 2 segments and you pay for both. Normalize to GSM-7 (Str::ascii() as a last resort) or budget for the extra segment.
Phone number format. Skebby wants the international prefix with or without + depending on endpoint version, and v4 covers two API families that may not agree. Test both. Strip spaces, dashes, and leading zeros (preg_replace('/\D/', '', $number)), and validate with a library rather than a regex.
Silent no-ops on invalid senders. An unapproved alphanumeric sender usually fails per-recipient inside an otherwise-successful batch response. Inspect the per-recipient result array, not the top-level one.
Delivery reports need two things: send_sms_plus and a callback URL configured in the Skebby dashboard (not in code). Register a route, verify the payload shape, and make it idempotent — callbacks can arrive out of order or twice.
Check credit before broadcasts, not after. With the User API in v4 this is now a genuine pre-flight check rather than a scrape. Abort the job if credit is low; otherwise you get a partial send and no way to know who was skipped without parsing every result row.
Debugging payloads: add a Guzzle middleware (or Http::withOptions(['debug' => true]) if you reimplement) to dump the exact form fields. Nine times out of ten the bug is sender_type mismatched with the sender value, or the recipient array being JSON-encoded when Skebby expects comma-separated.
Extension points, if you keep the bundle: fork it, add a SkebbyServiceProvider with mergeConfigFrom + publishes, and pin your fork with a VCS repository in composer.json. v4.0.0 reduces the abandonment risk but doesn't add Laravel support — if 4.1 moves the Symfony floor again, that's yours to fix at 2 a.m.
How can I help you explore Laravel packages today?