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

Laravel Iranian Sms Laravel Package

tartan/laravel-iranian-sms

Laravel package that unifies Iranian SMS gateways behind one driver-based API, letting you send messages through providers like Kavenegar and Melipayamak via Laravel notifications, queues, and simple config-driven setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require tartan/laravel-iranian-sms

Check the composer.json constraints before you commit. The package targets Laravel 5.x and the last tagged release was March 2021, so on Laravel 8/9/10+ expect to pin illuminate/support manually or fork it. On a modern app, treat this as a "vendor it and maintain it yourself" dependency, not a drop-in.

Publish and configure

php artisan vendor:publish

The package ships a single config file (something like config/sms.php) holding a default driver plus a drivers array keyed by operator — Kavenegar, Melipayamak, SMS.ir, Ghasedak, Farapayamak, etc. That config file is the first thing to read; it tells you exactly which operators are implemented and which credential keys each one wants.

Move credentials to .env immediately:

SMS_DRIVER=kavenegar
KAVENEGAR_API_KEY=...
KAVENEGAR_SENDER=10004346
// config/sms.php
'default' => env('SMS_DRIVER', 'kavenegar'),
'drivers' => [
    'kavenegar' => [
        'api_key' => env('KAVENEGAR_API_KEY'),
        'sender'  => env('KAVENEGAR_SENDER'),
    ],
],

Where to look first, in order

  1. config/sms.php — driver names and credential shape.
  2. src/ — the manager class and the Drivers/ directory. Each driver is a thin wrapper over one operator's REST API, so reading one driver end-to-end tells you the exact method signatures for the whole package.
  3. The README for the facade alias and the "one working example" — that is usually all the docs there is.

First use case: send one message to your own number from php artisan tinker via the facade, then hit the same provider's "credit" call to confirm the credentials work. Do this before you write a single line of application code — it separates config problems from integration problems for the rest of the project.

use Tartan\Sms\Facades\Sms;

Sms::send('09123456789', 'Test from tartan/laravel-iranian-sms');

If the facade alias isn't registered automatically on your Laravel version, add the provider and alias to config/app.php by hand.


Implementation Patterns

1. Treat the facade as an outbound port, not a utility

The fastest way to regret this package is calling Sms::send() from controllers. Wrap it once:

namespace App\Services;

use Tartan\Sms\Facades\Sms;

class SmsService
{
    public function send(string $to, string $message): void
    {
        Sms::send($this->normalize($to), $message);
    }

    public function sendOtp(string $to, string $code, string $template = 'otp'): void
    {
        // pattern/template send if the driver supports it
        Sms::sendPattern($this->normalize($to), $template, ['code' => $code]);
    }

    private function normalize(string $number): string
    {
        $number = preg_replace('/\D+/', '', $number);
        if (str_starts_with($number, '98'))   $number = '0' . substr($number, 2);
        if (str_starts_with($number, '9'))    $number = '0' . $number;
        return $number;
    }
}

Verify the exact method name (sendPattern, sendTemplate, sendOtp…) against src/Drivers/ for your version — naming differs per release, and pattern sends are the API that varies most between Iranian operators.

2. Register a Laravel Notification channel

This is the pattern that pays for itself. One channel, and every Notification class in the app can be SMS-capable:

// app/Channels/SmsChannel.php
class SmsChannel
{
    public function send($notifiable, Notification $notification)
    {
        if (! $to = $notifiable->routeNotificationFor('sms')) return;
        app(SmsService::class)->send($to, $notification->toSms($notifiable));
    }
}

Then via() returns [SmsChannel::class] alongside mail/database. OTPs, order confirmations and alerts all become ordinary Laravel notifications, and you get ShouldQueue on the notification for free.

3. Always queue, never block a request

Provider latency from Iran to these panels is unpredictable and 2–5s timeouts are common. Put the wrapper behind a job:

class SendSms implements ShouldQueue
{
    public $tries = 3;
    public $backoff = [10, 60, 300];
    // ...
}

Then combine with shouldQueue() notifications. Retries matter because a transient 502 from an operator panel is the single most common failure you will see.

4. Failover between operators

Because drivers are config-selected, failover is a try/catch plus a config()->set — or better, an explicit loop in your SmsService over a list of driver names:

foreach (config('sms.fallback') as $driver) {
    try {
        return Sms::driver($driver)->send($to, $message);
    } catch (\Throwable $e) {
        Log::warning("sms driver {$driver} failed", ['e' => $e->getMessage()]);
    }
}
throw new SmsDeliveryException('all drivers failed');

Keep at least two operators configured with separate credit balances if SMS is on a login path.

5. Persist every outbound message

The package does not log for you. Write a sent_sms row (number, driver, template, provider message-id, status) in the same place you call the facade. Delivery reports are asynchronous and webhook/lookup-based, so the local row is your only join key when a user says "I never got it."

6. Choose pattern sends for OTP and transactional copy

Free-text sends are subject to content filtering and are usually more expensive per part. Pattern (template) sends are pre-approved by the operator, cheaper, and far more reliable for OTP. Use free text only for internal/admin messaging you control.

7. Testing

  • Point SMS_DRIVER at log in phpunit.xml if the package ships a log/null driver.
  • If it doesn't, bind a fake in a test service provider, or Sms::shouldReceive('send') on the facade — the facade is the clean seam, which is another argument for not calling drivers directly.
  • Never point CI at a live operator. Credit burns fast and tests become flaky.

Gotchas and Tips

Laravel version pinning is the first landmine. Laravel 5.x era package with a 2021 release means replace/require constraints will fight your app on Laravel 9+. If you're on a modern stack, budget for a fork: the drivers are small and readable, and swapping illuminate/support constraints plus the service provider's register()/boot() signature is usually a 30-minute job.

Number normalization is not handled for you. Users type +98…, 0098…, 98…, 09…, and occasionally Persian/Arabic digits (۰۹۱۲…). Normalize to 09xxxxxxxxx at the boundary and use a single private method for it. Persian-digit input silently fails validation and silently fails the SMS panel's length calculation.

Message length is counted in parts, and Persian is not GSM-7. Persian text encodes as UCS-2, so a single SMS part is ~70 characters, not 160. A 3-part message costs 3× and can be dropped by some panels. Compute encoding-aware part counts before sending, and keep OTP/notification copy under 70 characters where possible.

Pattern sends need the template registered in the operator panel first. The template name and placeholder keys (%code%, {code}, positional) are operator-specific and are not normalized by this package. A mismatch throws a provider-side error that often surfaces as a bare array, not an exception — check the driver's return handling.

Return values are inconsistent across drivers. Some drivers throw, some return a [status, message] array, some return an object, some return true regardless. Do not assume try/catch covers everything — inspect the driver's return and wrap it in your service so the rest of the app sees one thing: success or exception.

Config caching. After any config/sms.php edit in production: php artisan config:clear / config:cache. Most "the driver change did nothing" reports are this.

Sender numbers are per-driver. A sender ID valid on Kavenegar is meaningless on Melipayamak. Keep sender in the driver config, not a global env var you reuse.

Delivery reports are best-effort. deliveryStatus/getStatus calls are rate-limited and depend on the operator having processed the report. Treat "pending" as normal, and never make a user-facing flow block on delivery confirmation.

Credit checks are cheap and worth scheduling. Each driver exposes a credit/remaining-balance call. Put it on a daily schedule and alert below a threshold — running dry on the OTP path during a product launch is the classic failure mode.

Extension points, in order of usefulness:

  1. Custom driver — implement the driver interface/abstract class, add it under the package's Drivers namespace (or your own), and register it in config/sms.php. Best route for an operator the package doesn't cover.
  2. Extend the manager — bind your own subclass to override driver resolution for failover/telemetry.
  3. Facade swap — bind your SmsService in the container and stop referencing the package anywhere else. This is the real answer if the package goes unmaintained: one file to rewrite, not fifty.

Debugging checklist when a send fails: (1) config:clear, (2) dump the resolved driver name, (3) curl the operator's endpoint directly with the same key/sender/body — if curl works and the package doesn't, it's the driver's payload, not your credentials, (4) check the sender is active on the panel, (5) check credit.

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib