toneflix-code/kudi-sms-php
A Laravel/PHP package for sending SMS through the Kudi SMS API. It offers a simple, framework-friendly interface for adding transactional or bulk messaging to your applications, with straightforward configuration and a clean API for quick integration.
Confirm the package is actually installed and what it exposes before designing around it:
composer require toneflix-code/kudi-sms-php:1.0.6
Then read, in this order:
composer.json — PHP version constraint, whether Guzzle/cURL is used, and whether there is a extra.laravel block (i.e. auto-discovery). With zero dependents and a single maintainer, the source is the documentation.src/ — the main client class, its constructor signature (API token? sender ID? base URI?), and the exact method names and return types.README.md — usually the only usage example.Provision credentials from your KudiSMS dashboard: an API token and an approved sender ID. The sender ID is not something you invent at call time — it must be registered/approved on the KudiSMS side, so request it before you write code.
Minimal first use case: a transactional message to a Nigerian MSK number.
// Namespace/class are illustrative — confirm from composer.json and src/.
use ToneflixCode\KudiSmsPhp\KudiSms;
$sms = new KudiSms(apiToken: config('services.kudisms.token'));
$response = $sms->send(
to: '2348030000000',
message: 'Your verification code is 123456',
senderID: config('services.kudisms.sender'), // note: `senderID` on >=1.0.6; `senderId` on <=1.0.5
);
// Inspect the raw response; don't assume a bool.
logger()->info('kudisms.send', ['response' => $response]);
If the constructor takes positional arguments, wrap the instantiation in your own factory so you are not coupled to the signature. Get one real message delivered end-to-end (including a non-Nigerian number rejected cleanly) before building abstractions on top.
On 1.0.6 specifically: the release only renames the parameter senderId → senderID. That is a no-op for positional callers and a hard Error/TypeError for anyone using named arguments. Before adopting, grep your codebase (and any published stubs/tests) for senderId and update. The rename does tell you the sender ID is accepted by the client (constructor and/or send()), so it's a required credential, not a dashboard-only setting.
Bind it in the container, don't new it in controllers. Because this is a framework-agnostic PHP wrapper, it almost certainly has no service provider. Register it yourself:
// app/Providers/AppServiceProvider.php
$this->app->singleton(KudiSms::class, fn () => new KudiSms(
token: config('services.kudisms.token'),
// sender/base URI if the constructor accepts them
));
Add config/services.php entries (token, sender, timeout) and matching .env keys (KUDISMS_TOKEN, KUDISMS_SENDER). Never hardcode the token.
Never pass the sender ID by named argument at the call site. The 1.0.6 rename is proof that this package's parameter names are not a stable contract — it is a typo fix in a 1.0.x patch, which under SemVer is technically a breaking change for named callers. Resolve the sender from config inside your adapter instead, so a future senderID → sender_id rename is a one-line diff in one class.
Hide it behind your own interface. The package has 0 stars and 0 dependents; treat it as replaceable. Define App\Contracts\SmsGateway with send(string $to, string $message): SmsResult and implement it with a KudiSmsGateway adapter. When you outgrow the wrapper (or it is abandoned), you swap one class, not every call site.
Integrate via a Laravel Notification channel. This is the highest-leverage day-to-day pattern: OTP, order updates, and payment confirmations all become notifications rather than direct calls.
class KudiSmsChannel
{
public function __construct(private SmsGateway $gateway) {}
public function send(object $notifiable, Notification $notification): void
{
$this->gateway->send($notifiable->routeNotificationFor('sms'), $notification->toSms($notifiable));
}
}
Then return [(new KudiSmsChannel(app(SmsGateway::class)))]; from via().
Queue bulk sends and chunk recipients. Whether the wrapper exposes a bulk endpoint or only a single-send method, do not loop synchronously inside a request. Push a job, chunk recipients (e.g. 50–100 per API call if bulk is supported, or sequential calls with a small delay otherwise), and record per-recipient results.
Persist an audit row per send. Store recipient, sender ID, message hash (not necessarily the full body, for privacy), provider response/message ID, and status. This is what makes debugging "did the customer get the OTP?" tractable and lets you reconcile against the KudiSMS dashboard.
Retry deliberately. Wrap sends in a job with $tries = 3 and exponential backoff, but only for transport errors and 5xx responses. A 4xx (bad number, unapproved sender, insufficient balance) must fail fast — retrying it burns money and time.
Fake it in tests. Bind a FakeSmsGateway implementing your contract in the test container and assert on recorded messages. Do not hit the live API from CI.
Upgrade with eyes open. 1.0.6 is a two-word diff; check out 1.0.5..1.0.6 anyway, since a single-maintainer project can slip behaviour changes into a "typo fix" commit. If you cannot upgrade immediately, pin 1.0.5 exactly rather than using ^1.0, which would silently pull the rename on your next composer update.
senderId → senderID. If you call with named arguments (senderId: $sender), this patch release breaks you at runtime. It will not show up in static analysis of the vendor class if you are passing it through dynamically, and it will not show up in tests that never exercise the real client. Grep for senderId, update, and pin the version in composer.json.0803…, +234803…, 234803…, and with stray spaces/dashes. Normalize to one canonical form in an adapter layer and confirm from the source which format KudiSMS expects. A wrong prefix often returns a 200 with an error body rather than an exception.{status: "error"} with a 200. Always parse the response payload and check the wrapper's success signal — or the absence of one. If the wrapper returns raw arrays, wrap that in your own SmsResult DTO with an ok flag.composer.json for extra.laravel; if it is absent, none of that is wired up, and any "config" file is something you create.composer.json should use an exact or tilde constraint rather than *. 1.0.6 is a concrete example of why: a cosmetic-looking patch was source-breaking for named-argument consumers. Vendor the repo if you need a fix upstream has not merged.SmsGateway interface from day one.How can I help you explore Laravel packages today?