Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sms Laravel Package

shetabit/sms

Laravel package for sending SMS through multiple gateways using a unified, driver-based API. Configure providers, send messages or OTPs, and switch SMS services without rewriting your application code.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and scaffold:

    composer require shetabit/sms
    php artisan vendor:publish --tag="sms-config"   # creates config/sms.php
    
  2. Put the credentials for the driver you actually intend to use in .env (never hardcode them in config/sms.php), and set the default driver:

    SMS_DRIVER=kavenegar
    KAVENEGAR_API_KEY=...
    
  3. Send one message from php artisan tinker before wiring anything into application code:

    use Shetabit\Sms\Facade\Sms;
    
    Sms::driver('kavenegar')->to(['09121234567'])->message('test')->send();
    

    If that tinker line works, everything else is just plumbing.

  4. First real use case: the OTP / verification-code path. It is the highest-traffic, highest-risk send in most Iranian-market apps, it is where provider pattern/template APIs matter most, and it is where a driver swap (v1.1.0's whole point) actually pays off.

Where to look first: config/sms.php (driver list and credential shape — this is the file that will bite you on upgrade), then the driver class for your provider under src/Drivers/. Read the driver's send() signature before copying README snippets, because the fluent surface and the raw per-driver API are not always 1:1.

Implementation Patterns

Pin the driver explicitly

Relying on sms.default makes environment behaviour implicit. In application code prefer the explicit form so a config change on the server can't silently reroute traffic:

Sms::driver(config('sms.default'))->to($numbers)->message($text)->send();

Keep sms.default as the environment switch (local/staging/prod), and pass the driver name explicitly anywhere the business logic depends on the provider.

Wrap the package behind one application service

The facade is a manager, not an abstraction boundary. Put a thin class in front of it so the rest of the app never imports Shetabit\Sms:

final class SmsService
{
    public function send(string $text, array $to, ?string $driver = null): void
    {
        $sms = Sms::driver($driver ?? config('sms.default'));

        $sms->to($to)->message($text)->send();
    }
}

This is the single most valuable pattern with this package: driver names, recipient-format quirks and provider-specific exceptions all get absorbed in one place, and the v1.1.0 IPPanel driver becomes a config change rather than a code change.

Queue the send, always

SMS HTTP calls are slow and flaky and will otherwise run inside the request that also has to render the OTP screen.

final class SendSms implements ShouldQueue
{
    use Queueable, InteractsWithQueue;

    public int $tries = 3;
    public int $backoff = 5;

    public function __construct(public string $text, public array $to) {}

    public function handle(SmsService $sms): void
    {
        $sms->send($this->text, $this->to);
    }
}

For OTP specifically, either send inline with a hard timeout and a short circuit, or queue with a very high priority queue — waiting 20 seconds for a code defeats the purpose.

Multiple drivers as failover, not as variety

v1.1.0's IPPanel driver is most useful as a second provider. The realistic production pattern is failover on the cheap providers that occasionally go down or hit credit limits:

public function send(string $text, array $to): void
{
    foreach (['kavenegar', 'ippanel'] as $driver) {
        try {
            Sms::driver($driver)->to($to)->message($text)->send();
            return;
        } catch (\Throwable $e) {
            report($e);
        }
    }

    throw new SmsFailedException('All SMS drivers failed.');
}

Only fail over on transport/credit errors — not on validation errors (bad number, malformed template), which will fail identically on every provider and just double your cost.

IPPanel specifics

IPPanel is an Iranian provider with two distinct send paths: a plain webservice send from a provisioned line, and pattern (template) sends where the message body is registered server-side and you pass variables. For OTP, use the pattern path — it is delivered on dedicated OTP lines, avoids content filtering delays, and is priced better. Credentials are username/password (or an API key depending on account type), so the ippanel block in config/sms.php will not look like the kavenegar block; do not try to normalise the arrays, treat each driver's block as opaque and read the driver source for the exact keys it expects.

Test by swapping the driver, not by mocking the facade

Give yourself a no-op driver in config/sms.php for the testing/local environment, or bind a fake SmsService in the container. Asserting on facade calls (Sms::shouldReceive(...)) tests the package, not your code — assert on your own service/queue instead.

Gotchas and Tips

  • The published config does not update itself. This is the main upgrade hazard with this package. config/sms.php is in your repo, but new drivers ship with new config blocks. After composer update, diff the package's config/sms.php against yours and hand-merge. Do not re-run vendor:publish blindly — it will overwrite your env key names and default driver.

  • v1.1.0 is additive and non-breaking. One new driver (ippanel), one new contributor, no interface changes, no removed config keys. Upgrading mid-sprint is safe; if you don't need IPPanel, upgrading is optional but harmless. If you do want it, the only work is adding the ippanel block and credentials — no code changes if you followed the service-wrapper pattern above.

  • Recipient formats are not consistent across drivers. Some take an array of strings, some a comma-separated string, some require the sender line separately. Normalise to E.164-ish (+98912...) or a single canonical form in your SmsService and convert per driver — otherwise a driver swap "works" in staging and silently fails on real numbers.

  • Iranian number normalisation. 09121234567, 9121234567, +989121234567 and 989121234567 are all the same human and all different strings. Normalise on input, before it ever reaches the package.

  • Errors are provider-shaped. Driver exceptions come from whatever HTTP client the driver wraps; there is no unified exception hierarchy you can rely on. Catch \Throwable at the service boundary, log the provider response body (most providers return a useful error payload), and wrap in your own exception type.

  • Watch credit before bulk sends. Providers fail partway through a list when credit runs out — you get half a campaign and no clean rollback. For anything over a few hundred messages, chunk the list, check remaining credit first if the driver exposes it, and record per-message success so a retry doesn't double-send.

  • Duplicate sends on retry. Queued jobs with $tries > 1 plus a provider that accepted the request but timed out on the response will produce duplicates. Make sends idempotent at your layer (a sent_at marker per recipient) rather than trusting the driver.

  • Extension point: adding your own provider is a matter of implementing the package's driver contract and registering it in the drivers array — worth doing for internal gateways rather than forking. The IPPanel PR in v1.1.0 is a good, small reference implementation to copy from.

  • Don't dd() inside a driver. Half of these are single-method classes; debugging by dumping in the driver tends to run inside a queued worker and produces silent timeouts instead of output. Log the request/response payload around the call site in your service wrapper instead.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons