class-atlas/laravel-usms
Laravel-USMS provides a clean, configurable integration for sending SMS through the USMS gateway. It offers a Laravel-friendly API, publishable configuration, and straightforward delivery handling for transactional and notification messages.
v3 is a small but breaking release: the only API change is that createBrandName() now requires a second $description argument. Everything else about the package (sending messages, credentials, config) is unchanged from v2.
composer require classatlas/laravel-usms:^3.0. Laravel package auto-discovery registers the service provider and the USms facade — check composer.json of the package if you need to register manually in an older app skeleton.php artisan vendor:publish --tag=usms-config or equivalent — confirm the tag in the package README) and put the uBill credentials in .env. Run php artisan config:clear after editing.config/usms.php, the USms facade docblock, BrandNameCreateCode (the statusID enum) and USmsRejectedException — those four files tell you almost everything about what the gateway can refuse and why.use ClassAtlas\USms\Facades\USms;
$brand = USms::createBrandName(
'MyBrand',
'https://mybrand.ge — payment reminders and notices to our customers'
);
Treat brand creation as one-time onboarding, not a runtime call: the description is reviewed by uBill, so the brand may not be usable immediately.
Wrap brand creation in an artisan command, not a controller. A one-off usms:create-brand command (or a seeder) keeps the description in version control, is re-runnable by ops, and avoids creating a new brand every deploy. Store the resulting brand name/id in config or a settings table and read from there at send time.
Validate the description before it hits the API. Mirror uBill's rules with a FormRequest or rule set so operators get a field error instead of a USmsRejectedException in the logs:
$request->validate([
'brand' => ['required', 'string'],
'description' => ['required', 'string', 'max:500'], // max:500 uses mb_strlen
]);
Prompt for the content uBill expects: website, Facebook/Instagram page or application, plus how the brand name will be used. A description that only says "test" or repeats the brand name will be refused.
Branch on the enum, not on strings. Both new refusals arrive as USmsRejectedException with their own BrandNameCreateCode case, so map them to actionable messages:
use ClassAtlas\USms\Exceptions\USmsRejectedException;
use ClassAtlas\USms\Enums\BrandNameCreateCode;
try {
USms::createBrandName($name, $description);
} catch (USmsRejectedException $e) {
$message = match ($e->code) {
BrandNameCreateCode::DESCRIPTION_REQUIRED => 'Add a description referencing your website or social page.',
BrandNameCreateCode::DESCRIPTION_TOO_LONG => 'Description must be 500 characters or fewer.',
default => 'uBill rejected the brand name: '.$e->getMessage(),
};
}
(Confirm the exact property name on the exception — code/statusId/getCode() — in the package source; the enum cases themselves are DESCRIPTION_REQUIRED = 60 and DESCRIPTION_TOO_LONG = 61.)
Centralise the facade behind a thin service. A BrandRegistrar / SmsSender class with constructor-injected config makes it trivial to swap in a fake in tests and keeps the two-argument call in one place if the signature moves again.
Send asynchronously. Message sending belongs in a queued job with sensible retries and a dead-letter path; catch USmsRejectedException inside the job and mark it failed permanently rather than retrying a deterministic rejection.
Testing. Assert the description is actually forwarded, because that's the whole point of v3:
USms::shouldReceive('createBrandName')
->once()
->with('MyBrand', Mockery::on(fn ($d) => str_contains($d, 'mybrand.ge')));
If the package ships a USms::fake(), prefer it; otherwise bind a Mockery mock of the underlying client into the container.
ArgumentCountError before it ever reaches uBill — and the old one-argument behaviour would be refused by the API anyway. Grep for createBrandName including tests, factories, seeders and mocks. Mockery expectations with ->with('MyBrand') will fail with an argument-count mismatch until updated.max:500 rule (which uses mb_strlen) rather than a manual strlen() check, or you'll reject valid descriptions or accept invalid ones.max:500 is not the same as "good enough". The description is judged on content: it must name the website, Facebook/Instagram page or application and explain the intended use. Treat DESCRIPTION_REQUIRED as a content-quality refusal, not just an empty-field one.USmsRejectedException surfaces, log statusID plus the brand name, never the raw credential or the full request payload. Add a case to your match when uBill adds new codes, otherwise they fall through to default.env() calls in the package config only resolve when the config isn't cached — after changing credentials, run php artisan config:cache on deploy rather than assuming .env edits take effect.^3.0 stops a future silent break; this release is the proof that the package will make required-argument changes when the gateway forces it.How can I help you explore Laravel packages today?