laravel-sms maintained by gennet
Gennet Laravel SMS
Official standalone Laravel SDK for the Gennet SMS API.
This package talks to the Gennet SMS API directly over HTTP (Guzzle). It does not
depend on the generic gennet/sms PHP SDK — it is self-contained and installs on its
own into any Laravel 11/12/13 application.
Requirements
- PHP >= 8.1
- Laravel 11, 12 or 13
Installation
composer require gennet/laravel-sms
The service provider and GennetSms facade are auto-discovered.
Publish the config file:
php artisan vendor:publish --tag=gennet-sms-config
Configuration
Add to your .env:
GENNET_SMS_API_TOKEN=
GENNET_SMS_SENDER_ID=GENNET
GENNET_SMS_BASE_URL=https://isms.gennet.com.bd
GENNET_SMS_TIMEOUT=15
GENNET_SMS_CONNECT_TIMEOUT=5
Endpoint paths are also configurable and default to the verified Gennet SMS API v3 routes:
GENNET_SMS_ENDPOINT_SEND=/api/v3/send-sms
GENNET_SMS_ENDPOINT_BULK=/api/v3/send-sms/bulk
GENNET_SMS_ENDPOINT_DYNAMIC=/api/v3/send-sms/dynamic
GENNET_SMS_ENDPOINT_STATUS=/api/v3/send-sms/status
GENNET_SMS_ENDPOINT_BALANCE=/api/v3/balance
GENNET_SMS_ENDPOINT_TRANSFER=/api/v3/transfer-balance
GENNET_SMS_ENDPOINT_INCOMING=/api/v3/incoming-messages-list
Never commit real tokens or credentials to source control.
Usage
Single SMS
use Gennet\LaravelSms\Facades\GennetSms;
$response = GennetSms::send(
msisdn: '8801712345678',
message: 'Hello from Laravel'
);
A secure 20-character alphanumeric csms_id is generated automatically when omitted.
You may provide your own, or override the configured sender ID per call:
$response = GennetSms::send(
msisdn: '8801712345678',
message: 'Your OTP is 123456',
csmsId: 'OTP123456'
);
$response = GennetSms::send(
msisdn: '8801712345678',
message: 'Hello',
csmsId: 'ORDER1001',
sid: 'GENNET'
);
Bulk SMS
GennetSms::bulk(
msisdns: ['8801712345678', '8801812345678'],
message: 'Hello everyone',
batchCsmsId: 'BATCH1001'
);
Dynamic (personalized) SMS
GennetSms::dynamic([
['msisdn' => '8801712345678', 'message' => 'Hello Karim', 'csms_id' => 'MSG1001'],
['msisdn' => '8801812345678', 'message' => 'Hello Rahim', 'csms_id' => 'MSG1002'],
]);
The Laravel-friendly message key is mapped internally to the API's text field.
Delivery status (DLR)
GennetSms::status(referenceId: '...');
Balance
$response = GennetSms::balance();
Balance transfer
GennetSms::transferBalance(
fromSid: 'SID1',
toSid: 'SID2',
amount: 1000,
remarks: 'Transfer to child account'
);
Incoming messages
GennetSms::incoming();
The production backend currently only requires/validates api_token for this
endpoint. Additional array keys passed to incoming() are forwarded as extra
query parameters for forward compatibility, but are not guaranteed to be
understood by the API.
Dependency injection
use Gennet\LaravelSms\Contracts\GennetSmsContract;
class SmsService
{
public function __construct(
private GennetSmsContract $sms
) {}
public function send(): void
{
$this->sms->send('8801712345678', 'Hello');
}
}
Laravel Notification channel
use Gennet\LaravelSms\Notifications\GennetSmsChannel;
use Gennet\LaravelSms\Notifications\GennetSmsMessage;
class OtpNotification extends \Illuminate\Notifications\Notification
{
public function via(object $notifiable): array
{
return [GennetSmsChannel::class];
}
public function toGennetSms(object $notifiable): GennetSmsMessage
{
return new GennetSmsMessage('Your OTP is 123456');
}
}
class User extends Authenticatable
{
public function routeNotificationForGennetSms(): string
{
return $this->phone;
}
}
GennetSmsMessage also supports ->from('SENDERID') and ->clientReference('CSMS1001').
Exception handling
HTTP 200 does not always mean success — the Gennet API can return status: "FAILED"
with a status_code inside a 200 response. This SDK detects that and throws a typed
exception instead:
use Gennet\LaravelSms\Exceptions\ApiException;
use Gennet\LaravelSms\Exceptions\RateLimitException;
use Gennet\LaravelSms\Exceptions\InsufficientBalanceException;
use Gennet\LaravelSms\Exceptions\AuthenticationException;
use Gennet\LaravelSms\Exceptions\TransportException;
try {
GennetSms::send(msisdn: '8801712345678', message: 'Hello');
} catch (RateLimitException $e) {
// status_code 4029 — retry later
} catch (InsufficientBalanceException $e) {
// status_code 4008
} catch (AuthenticationException $e) {
// status_code 4001 — invalid API token
} catch (TransportException $e) {
// network/DNS/timeout failure — no response was received
} catch (ApiException $e) {
// any other API-level failure
$e->apiStatusCode();
$e->payload();
}
The full exception hierarchy: GennetSmsException → ApiException →
AuthenticationException, ValidationException, RateLimitException,
InsufficientBalanceException, ServerException. TransportException is thrown
directly from GennetSmsException for connection-level failures.
No automatic retries. send, bulk, dynamic and transferBalance are never
retried automatically by this SDK, because retrying could cause duplicate SMS
deliveries or duplicate balance transfers. Retry only if your application logic
explicitly decides to.
Verified API routes
All endpoint defaults in this package were verified directly against the production Gennet SMS API v3 route definitions and controller/request classes — none were guessed:
| Operation | Method | Path |
|---|---|---|
| Single SMS | POST | /api/v3/send-sms |
| Bulk SMS | POST | /api/v3/send-sms/bulk |
| Dynamic SMS | POST | /api/v3/send-sms/dynamic |
| Status (DLR) | GET | /api/v3/send-sms/status |
| Balance | GET | /api/v3/balance |
| Transfer balance | POST | /api/v3/transfer-balance |
| Incoming messages | GET | /api/v3/incoming-messages-list |
Security
- API tokens are sent via the
X-API-TOKENheader and asapi_tokenin the request body/query (matching the backend's accepted contract) — never logged. - Exception messages and payloads never include the API token.
- No automatic retries on send/bulk/dynamic/transfer operations.
- HTTPS is the default transport (
https://isms.gennet.com.bd).
Testing
composer install
composer test
Tests use Orchestra Testbench and a Guzzle MockHandler — no real API calls are made.
License
MIT