ipe/smsir-php
ipe/smsir-php is a PHP client for the SMS.ir API, offering a simple way to send SMS messages, handle verification codes, manage contacts and groups, and integrate SMS.ir into Laravel or standalone PHP applications with straightforward configuration.
composer require ipe/smsir-php:^1.0
src/ before the README. With packages this small the source is the documentation; there are usually only two or three classes (a client plus one or two value objects). Confirm the constructor signature (API key position, base URL override, timeout/HTTP client injection) and whether it ships a service provider.$client = new SmsIr(env('SMSIR_API_KEY'));
$response = $client->send($mobile, 'test message');
Dump the full response object. You need to see the raw shape (status, message, data.messageId, etc.) before you write any error handling — the SDK's return type is what everything else depends on.Don't build the abstraction layer yet. Prove credentials, number format, and response shape on a single send, then move on.
Bind the client in the container, configure from env. Even if the package ships no service provider, register it yourself so you never construct it inline:
// AppServiceProvider::register()
$this->app->singleton(SmsIr::class, fn () => new SmsIr(
config('services.smsir.api_key')
));
Add SMSIR_API_KEY to config/services.php and .env.example. Never read env() outside config — config:cache will silently return null in production.
Wrap it in a single application-level gateway. One SmsGateway class (interface + SMS.ir implementation) that owns the SDK. This is your seam for testing, for later migrating to SMS.ir's v2 API, and for swapping providers. Keep the interface domain-shaped (sendOtp, sendBulk, getCredit), not SDK-shaped.
OTP flow — the pattern that matters most. Generate the code, persist it with an expiry and attempt counter before sending, then send via the template endpoint. Never store the plaintext code in logs. Verification of the code is your business logic; the SDK only delivers it.
Laravel notification channel. A custom channel wrapping the gateway fits naturally into Notifiable:
public function send($notifiable, SmsMessage $notification)
{
$this->gateway->send($notifiable->routeNotificationFor('sms'), $notification->body);
}
This keeps SMS out of controllers and gives you via() to route per-user preference.
Queue bulk sends. Any send over a handful of recipients belongs in a job with $tries, $backoff, and explicit timeout. Set a timeout longer than the SDK's HTTP timeout so the job doesn't get killed mid-request and you don't know whether it landed.
Persist every send. A sms_messages table (message_id, mobile, template, status, cost, raw_response) is the cheapest debugging tool you will build. When a customer says "I never got it," you need the message ID to take to the SMS.ir panel.
Testing. Fake the gateway interface in feature tests. For unit tests of the gateway itself, inject a mock HTTP client if the SDK accepts one; otherwise wrap the SDK call in a protected method and override it in a test double. Never let the test suite touch the real API key.
status field, not $response->getStatusCode(), and throw a domain exception on non-1 values.+98912…, 0098912…, and 0912… all arrive from forms. Strip to the local 09… format (or whatever the panel expects) in one place and validate with a regex before it reaches the API — otherwise you get opaque per-recipient failures inside a bulk call.getCredit before large campaigns.php artisan vendor:publish works. Check composer.json for extra.laravel. If absent, you own the registration.How can I help you explore Laravel packages today?