toneflix-code/kudisms-notification
Send SMS notifications from your Laravel app through Kudisms. This package adds a notification channel plus a lightweight API client, so you can deliver messages to one or many recipients using a simple, familiar Notification class.
1. Confirm your Laravel/PHP floor before you run anything. Version 2.0 requires PHP ^8.3|^8.4 and Illuminate ^12.0|^13.0. On Laravel 10 or 11 this package simply will not install — you stay on toneflix-code/kudisms-notification:1.1.1, which is now on an unmaintained branch. Make that an explicit decision, not a surprise during a composer update.
2. Install under the new name.
composer require toneflix/kudisms-notification:^2.0
Auto-discovery is unchanged in spirit, but every class string moved namespace: ToneflixCode\KudiSmsNotification → Toneflix\KudiSmsNotification. Read three files in vendor/toneflix/kudisms-notification/ before writing code:
src/*ServiceProvider.php — the exact provider FQCN (you need it verbatim for vendor:publish), the channel key registered (kudisms?), and any publish tags.config/*.php — the env keys (token vs api_key, sender_id vs from, endpoint). Re-publish rather than hand-editing; the 2.0 config may carry updated comments/keys.src/*Message.php — the fluent builder (content(), from(), to()…), which is the class you'll type most.3. Publish config with the new provider name.
php artisan vendor:publish --provider="Toneflix\KudiSmsNotification\KudiSmsNotificationServiceProvider"
If that returns "provider not found", the class was renamed in 2.0 — grab the FQCN from step 2 rather than guessing.
KUDI_SMS_TOKEN=your-kudisms-api-token
KUDI_SMS_SENDER_ID=MyApp
4. Upgrading an existing app (this is a rename, not an in-place bump).
composer remove toneflix-code/kudisms-notification
composer require toneflix/kudisms-notification:^2.0
Composer will not resolve the old name to the new one — it's a different package, so no composer update will ever pull 2.0 for you. Then:
grep -rn "ToneflixCode" app/ config/ tests/ database/ and fix imports. Notification classes that reference KudiSmsMessage are the usual suspects.config/kudisms.php from 1.x is fine to delete.php artisan optimize:clear — the cached package manifest in bootstrap/cache/ still points at the old provider and produces a confusing "class not found" on boot.5. First use case: OTP delivery on the User model.
// app/Models/User.php
public function routeNotificationForKudiSms($notification)
{
return $this->phone_number;
}
// app/Notifications/SendOtp.php
use Toneflix\KudiSmsNotification\KudiSmsMessage;
class SendOtp extends Notification implements ShouldQueue
{
public function __construct(public string $code) {}
public function via($notifiable): array
{
return ['kudisms'];
}
public function toKudiSms($notifiable)
{
return (new KudiSmsMessage)
->from(config('kudisms.sender_id'))
->content("Your verification code is {$this->code}. Expires in 10 minutes.");
}
}
The channel key and the to<StudlyChannel>() method must match exactly; if 2.0 changed the registration key, both the via() string and the method name move together.
Dedicated notification classes per intent. Don't build one generic SmsNotification with a $body argument; toKudiSms() is a template method, so keep OTP, delivery updates, and dunning as separate classes. It makes Notification::fake() assertions readable later.
On-demand sends without a notifiable model.
Notification::route('kudisms', '+2348030000000')
->notify(new OrderShipped($order));
Thin service wrapper for non-notification sends. If the package exposes its HTTP client (or you bind your own), wrap it so bulk operations don't leak through the notification layer:
final class SmsService
{
public function __construct(private KudiSmsClient $client) {}
public function send(string $to, string $message, ?string $sender = null): bool
{
return $this->client->send($to, $message, $sender ?? config('kudisms.sender_id'));
}
}
Type-hint the client by its new namespace; a stale use here is the most common post-upgrade fatal.
Always queue and always rate-limit. Transactional SMS is slow (200–800ms per round-trip) and providers throttle. Combine ShouldQueue with middleware:
public function middleware($notifiable): array
{
return [(new RateLimited('sms'))->dontRelease()];
}
Define the limiter with RateLimiter::for('sms', fn ($job) => Limit::perMinute(30)).
Test with Notification::fake() plus a fake HTTP layer. Assert the right person got the right notification, then separately assert payload shape against Http::fake():
Notification::fake();
$user->notify(new SendOtp('482913'));
Notification::assertSentTo($user, SendOtp::class);
Post-upgrade, run the suite before touching anything else — passing tests are your proof the namespace swap is complete.
Fallback channel. For OTPs consider return ['kudisms', 'mail'] with a toMail() on the same class — cheap insurance at the cost of one extra method.
Audit trail. Log the send yourself in an Illuminate\Notifications\Events\NotificationSent listener rather than inside every notification class.
The 2.0 upgrade is a package rename plus a namespace move — treat it as two migrations. Miss either half and you get either a composer.json pointing at a dead package or a Class "ToneflixCode\KudiSmsNotification\KudiSmsMessage" not found at runtime. Grep for both the old vendor string and the old namespace, including published config comments and any queue.php/notifications.php references.
Clear the package manifest, not just config. After swapping the package, bootstrap/cache/packages.php can still list the old provider. Symptom: config('kudisms.*') returns null and the channel key isn't registered, with no error explaining why. php artisan optimize:clear fixes it; config:clear alone doesn't.
Environment floor is a hard gate. If any app in your fleet is on Laravel 10/11 or PHP 8.1/8.2, 2.0 is unavailable to it — and 1.1.1 is now a frozen branch. If you're multi-app, pin per app (toneflix-code/kudisms-notification:1.1.* on the old ones) and keep your usage behind a single wrapper class so the divergence stays one file wide.
Nothing in 2.0 changes the runtime behaviour of the channel — no new builder methods, no new config keys surfaced in the release notes. So any pre-existing bug or quirk you worked around in 1.x is still there: phone normalisation, 200-with-error-body responses, sender-ID limits, character encoding costs. Don't re-read this release as a fix list.
Phone number normalisation is still the #1 failure mode. Nigerian gateways expect 234803... (no +, no leading 0) while your DB probably stores 0803.... Normalise inside routeNotificationForKudiSms(), not in the notification, so every channel benefits. Validate with a regex before dispatching — a malformed recipient often returns 200 OK with a body-level error rather than a 4xx.
The API probably returns 200 on failure. Most KUDI-style gateways respond {"status": "error", "message": "..."} with HTTP 200. A non-throwing Guzzle call does not mean delivery. Check the status/code field and throw so the queue retries — otherwise failed OTPs vanish silently.
Sender ID registration is out-of-band. Alphanumeric sender IDs must be pre-approved on the KUDI dashboard and are typically capped at 11 characters. A wrong sender ID fails at the API layer, not at config validation, so your first staging send is the real test.
Character encoding drives cost. GSM-7 gives 160 chars per segment; one curly quote, emoji, or ₦ drops you to UCS-2 at 70 chars/segment and can triple the bill. Keep templates ASCII-only, add a length guard, and strip \n — some gateways truncate at line breaks.
config:cache will bite you. If the provider reads env vars at boot instead of in the config file, cached config with a missing env var yields a null token at runtime with no error. After adding credentials or upgrading, always php artisan config:clear locally.
Maturity risk is essentially unchanged by the rename. A new vendor name and a Laravel 13 support bump are housekeeping, not community pressure-testing — the package still has negligible dependent count. Pin exactly ("toneflix/kudisms-notification": "2.0.*"), and keep usage concentrated in one notification class or one service wrapper so you can drop to raw Http::withToken() in an afternoon. If the client is constructor-injected and container-bound, you can substitute your own implementation without touching notification classes.
Extension points, in order of usefulness: (1) bind your own HTTP client / gateway wrapper in the container; (2) decorate the channel class with a try/catch + logging wrapper; (3) override config at runtime with config(['kudisms.endpoint' => ...]) for a proxy or sandbox host.
Debugging checklist when a message doesn't arrive: php artisan queue:failed → storage/logs/laravel.log → Http::fake() a test with Http::assertSent(fn ($r) => dd($r->data())) to dump the exact payload → check the KUDI dashboard for the send record. The dashboard is usually faster than log spelunking because it shows provider-side rejection reasons.
How can I help you explore Laravel packages today?