Search by

irnoti / laravel-sms-panel-api

shahkochaki

Official Laravel SDK for the irnoti SMS Panel public API — send SMS, run scheduled/gradual/regional/targeted/smart/peyamap/link-tracker campaigns, and sync your phonebook with a clean, typed, fluent client.

dev-main 2026-09-12 14:01 UTC

This package is not auto-updated.

Last update: 2026-10-12 05:46:23 UTC


README

irnoti

Laravel SDK for the irnoti SMS Panel API

Official, typed, fluent Laravel client for irnoti's public SMS panel API — send messages, run every campaign mode the dashboard offers, and sync your phonebook, without writing a single HTTP request by hand.

Tests Latest Version Total Downloads License

Why this package

irnoti gives every customer a REST API keyed by a per-account access key instead of a username/password — the same API documented at irnoti.com/developers. This SDK is a thin, typed Laravel wrapper around that API, so your application talks to irnoti through readable PHP method calls instead of hand-built HTTP requests and ad-hoc JSON parsing:

Irnoti::sms()->send(to: '09121234567', message: 'Your order has shipped!');
  • Full API coverage — every endpoint irnoti exposes today: account, message archive, single/bulk send, and all seven campaign modes (scheduled, gradual, regional, targeted, peyamap, smart, link-tracker), plus phonebook contacts/groups.
  • One exception type, stable error codes — every API error becomes an IrnotiApiException carrying the same namespaced error.code the raw API returns (sms.insufficient_balance, auth.invalid_key, …), with convenience checks so you rarely need to compare strings.
  • Laravel-native — auto-discovered service provider, a facade, a publishable config file, and requests made through Laravel's own HTTP client (Illuminate\Http\Client), so Http::fake() works out of the box in your tests.
  • Multi-tenant friendly — bind a client to any access key at runtime with withApiKey(), for apps that hold one irnoti key per customer rather than one global key.
  • Zero surprises — the SDK does not guess at business rules (pricing, ability scopes, recipient caps); it only shapes and sends the request the same way the API reference describes, and surfaces exactly what irnoti answers.

Requirements

  • PHP 8.2+
  • Laravel 10, 11 or 12 (illuminate/support + illuminate/http)
  • An irnoti account with an access key, minted from /dashboard/api-keys on your panel

Installation

composer require irnoti/laravel-sms-panel-api

The service provider and Irnoti facade are auto-discovered — no manual registration needed.

Publish the config file (optional; sensible defaults come from environment variables):

php artisan vendor:publish --tag=sms-panel-api-config

Add your access key to .env:

IRNOTI_API_KEY=irnt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
IRNOTI_API_BASE_URL=https://api.irnoti.com

Treat the access key like a password. It is only ever shown once, at creation time, in /dashboard/api-keys — irnoti stores nothing but its hash. Revoke and reissue it from that same page if it's ever exposed.

Quick start

use Irnoti\SmsPanelApi\Facades\Irnoti;
use Irnoti\SmsPanelApi\Exceptions\IrnotiApiException;

try {
    $credit = Irnoti::account()->credit();
    // ['wallet_balance_toman' => 128000, 'panel_credit_sms' => 940, ...]

    $sent = Irnoti::sms()->send(
        to: '09121234567',
        message: 'Your verification code is 48213.',
    );
    // ['id' => 5821, 'status' => 'queued', 'cost_toman' => 150]
} catch (IrnotiApiException $e) {
    report($e);
}

Or inject Irnoti\SmsPanelApi\IrnotiClient wherever you'd rather avoid the facade — it's bound as a singleton in the container.

Usage

Every resource method returns the plain PHP array decoded from the API's data field (see each method's PHPDoc for its exact shape) or throws IrnotiApiException on failure. List endpoints always come back as ['items' => [...], 'meta'? => [...]].

Account

Irnoti::account()->credit();   // wallet balance + upstream panel credit
Irnoti::account()->numbers();  // sender numbers available to this account

Message archive

Irnoti::messages()->list(perPage: 50);
Irnoti::messages()->get(id: 5821);

Single & bulk send

// One recipient, billed immediately. Requires the `sms.send` ability.
Irnoti::sms()->send(to: '09121234567', message: 'Hello!', from: '3000123456');

// Up to 200 recipients — mix raw numbers with phonebook group ids.
// Requires the `sms.bulk` ability.
Irnoti::sms()->bulk(
    numbers: ['09121234567', '09129876543'],
    message: 'Flash sale, today only!',
    groupIds: [12],
);

Scheduled send — scheduled.sms

Registered with the SMS provider itself, so the send fires even if your app is offline; cancel only works before it fires.

Irnoti::scheduled()->list();

Irnoti::scheduled()->create(
    scheduledAt: '2026-09-20 09:00:00',
    message: 'Reminder: your subscription renews tomorrow.',
    numbers: ['09121234567'],
);

Irnoti::scheduled()->cancel(id: 91); // full refund if it hasn't fired yet

Gradual send — sms.gradual

Drains up to 20,000 recipients in chunks over time, run on irnoti's own side — pause and resume at will.

Irnoti::gradual()->create(
    chunkSize: 200,
    intervalMinutes: 5,
    message: 'New collection just dropped.',
    groupIds: [3],
);

Irnoti::gradual()->pause(id: 44);
Irnoti::gradual()->resume(id: 44);
Irnoti::gradual()->cancel(id: 44); // refunds the undrained portion

Regional send — sms.regional

No recipient list — target a province (+ optional city) and a message count; irnoti's provider picks the numbers.

Irnoti::regional()->create(
    province: 'تهران',
    count: 500,
    message: "Today's special offer, just for Tehran.",
);

Targeted send — sms.targeted

The demographic sibling of regional send — filter by gender, age range, province/city, operator, or job (at least one filter is required).

Irnoti::targeted()->create(
    count: 1000,
    message: 'A deal built for you.',
    gender: 'male',
    ageMin: 20,
    ageMax: 35,
    province: 'اصفهان',
);

Peyamap send — sms.peyamap

Target one mobile operator's postal-code bank across one or more map areas, each with its own count.

Irnoti::peyamap()->create(
    operator: 'mci',
    areas: [
        ['name' => 'District 3', 'count' => 300],
        ['name' => 'District 6', 'count' => 200],
    ],
    message: 'Grand opening near you!',
);

Smart send — sms.smart

Your own recipient list, delivered "smart": a push notification first, a billed SMS fallback only for whoever didn't see it. Immediate-only; pull the delivery split on demand.

$send = Irnoti::smart()->create(message: 'Your appointment is tomorrow at 5 PM.', numbers: ['09121234567']);

$status = Irnoti::smart()->refresh(id: $send['id']);
// ['smart_count' => 1, 'sms_count' => 0, 'failed_count' => 0, 'pending_count' => 0, ...]

Link tracker — sms.link_tracker

Your own recipient list + a destination URL; irnoti mints a unique short link per recipient and reports clicks back.

$campaign = Irnoti::linkTracker()->create(
    targetUrl: 'https://yourshop.example/sale',
    message: 'Check out our sale: {link}',
    numbers: ['09121234567', '09129876543'],
);

$analytics = Irnoti::linkTracker()->get(id: $campaign['id']);
// ['total_clicks' => 2, 'ctr_percent' => 100.0, 'links' => [...], ...]

Phonebook

Irnoti::phonebook()->contacts()->list(keyword: 'Ali');
Irnoti::phonebook()->contacts()->create(mobile: '09121234567', attributes: ['first_name' => 'Ali'], groupIds: [1]);
Irnoti::phonebook()->contacts()->update(id: 7, attributes: ['company' => 'Acme']);
Irnoti::phonebook()->contacts()->delete(id: 7);

Irnoti::phonebook()->groups()->list();
Irnoti::phonebook()->groups()->create(name: 'VIP customers');

Error handling

Every failure — validation, an unauthenticated key, an insufficient wallet, a rate limit — is raised as a single Irnoti\SmsPanelApi\Exceptions\IrnotiApiException, carrying the exact fields the API answered with:

use Irnoti\SmsPanelApi\Exceptions\IrnotiApiException;

try {
    Irnoti::sms()->send(to: '09121234567', message: str_repeat('x', 1000));
} catch (IrnotiApiException $e) {
    $e->errorCode();      // e.g. "validation.failed"
    $e->statusCode();     // e.g. 422
    $e->details();        // e.g. ['message' => ['The message field must not be greater than 600 characters.']]
    $e->getMessage();     // English, human-readable

    // Convenience checks for the most common cases:
    $e->isInsufficientBalance();
    $e->isRateLimited();
    $e->isUnauthenticated();
    $e->isValidationError();
}

See the full error.code reference on irnoti.com/developers.

Multi-tenant usage

If your application holds one irnoti access key per customer rather than a single global key, don't rely on the config-bound singleton — bind a client to the key you need at request time:

use Irnoti\SmsPanelApi\Facades\Irnoti;

$client = Irnoti::withApiKey($customer->irnoti_api_key);

$client->sms()->send(to: $customer->mobile, message: 'Welcome aboard!');

withApiKey() returns a new, independent client — it never mutates the shared singleton.

Configuration reference

config/sms-panel-api.php:

Key Environment variable Default Description
base_url IRNOTI_API_BASE_URL https://api.irnoti.com Root of the panel API.
key IRNOTI_API_KEY — Your access key from /dashboard/api-keys.
timeout IRNOTI_API_TIMEOUT 15 Per-request timeout, in seconds.
retry.times IRNOTI_API_RETRY_TIMES 1 Attempts for transient network failures (1 disables retrying).
retry.sleep IRNOTI_API_RETRY_SLEEP 200 Delay between retries, in milliseconds.

Testing

composer test

The suite is built on Orchestra Testbench and Laravel's Http::fake() — no real access key or network access required. See tests/ for examples of faking success and error responses per resource.

Security

If you discover a security vulnerability in this SDK, please email security@irnoti.com rather than opening a public issue.

Contributing

Contributions are welcome — see CONTRIBUTING.md.

Changelog

See CHANGELOG.md for release notes.

License

Released under the MIT License.

Built and maintained by irnoti — full API reference at irnoti.com/developers.