iqbalhasandev/laravel-bulksmsbd
A Laravel package for sending SMS through the BulkSMSBD gateway. It wraps the BulkSMSBD API in a simple, configurable integration so you can dispatch single or bulk messages from your Laravel app with minimal setup.
This is a thin convenience wrapper around the BulkSMSBD HTTP API, not an ecosystem-level SMS abstraction. With 21 stars, zero dependents, and an archived repo, treat it as a starting point you will likely own and fork, not a dependency you can rely on for the long term. The day-to-day value is small: it saves you from hand-rolling one Http::get() call. Budget ten minutes to read src/ before wiring it in, because the README is sparse and the public surface is tiny.
Minimal setup
require is a future broken build:
composer require iqbalhasandev/laravel-bulksmsbd:^1.0
boot() for the exact tag; it is typically:
php artisan vendor:publish --provider="Iqbalhasandev\BulkSmsBd\BulkSmsBdServiceProvider"
.env. Read config/bulksmsbd.php first — the key names are whatever that file reads, commonly BULKSMSBD_API_KEY and BULKSMSBD_SENDER_ID:
BULKSMSBD_API_KEY=your_api_key
BULKSMSBD_SENDER_ID=your_approved_sender
BulkSmsBd::send($number, $message) or a resolvable BulkSmsBdService class.Where to look first
config/bulksmsbd.php — endpoint, credentials, sender ID, any type (text vs unicode) default.src/ — the actual send method and its return shape. This determines everything downstream.app() the service class.First use case
The realistic first use is a single transactional message: an OTP on login or an order-confirmation SMS. Verify the whole path once with php artisan tinker against a real number before building anything on top:
BulkSmsBd::send('8801XXXXXXXXX', 'Test message from staging');
If that returns something you can interpret (bool, array, response object), you know enough to proceed.
Pattern 1 — Direct call, immediately wrapped
Do not scatter facade calls through controllers. Put one seam between your app and the package on day one:
interface SmsSender
{
public function send(string $number, string $message): bool;
}
class BulkSmsBdSender implements SmsSender
{
public function send(string $number, string $message): bool
{
$response = BulkSmsBd::send($number, $message);
// Normalize the package's return shape to a bool here.
return (bool) ($response['response_code'] ?? $response);
}
}
Bind it in a service provider. When the package dies (it is archived, so plan for it), you swap one class instead of grepping the codebase.
Pattern 2 — Queued job with retry
The package makes a synchronous HTTP call. Never do that inline in a request that a user is waiting on:
class SendSms implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
public array $backoff = [10, 60, 300];
public function __construct(
public string $number,
public string $message,
) {}
public function handle(SmsSender $sms): void
{
if (! $sms->send($this->number, $this->message)) {
$this->release(60);
}
}
}
Note that release() retries forever unless you also fail explicitly — check $this->attempts() if the package cannot distinguish "temporary failure" from "bad number".
Pattern 3 — Notification channel
If the package ships a notification channel (check src/Channels or the README), prefer it for user-facing messages so SMS sits alongside mail and database notifications:
public function via($notifiable): array
{
return [BulkSmsBdChannel::class];
}
public function toBulkSmsBd($notifiable): string
{
return "Your order #{$this->order->id} has shipped.";
}
The notifiable needs a phone attribute the channel can find — usually phone or phone_number. Verify which one the channel reads before renaming columns.
Pattern 4 — Bulk sends
BulkSMSBD accepts comma-separated numbers in a single request. Use that for announcements instead of looping, but chunk to a sane size (50–100 per request) so one bad number does not poison the whole batch, and so a timeout does not lose an hour of sends.
Pattern 5 — Bangla / Unicode content
The API distinguishes text and unicode type. If you send Bangla, you must set the unicode type, otherwise the message arrives as garbage. Check whether the package exposes that parameter; if not, this is your first fork patch.
Pattern 6 — Testing
Never hit the live API in tests. Fake the underlying HTTP call if the package uses the Laravel HTTP client:
Http::fake([
'bulksmsbd.net/*' => Http::response(['response_code' => 202], 200),
]);
If it uses raw cURL or Guzzle directly, bind a fake SmsSender in the container instead and assert against that. This is another argument for the wrapper in Pattern 1.
The archive status is the headline risk. There is no Laravel 11/12 compatibility guarantee and no security patches. Check composer.json constraints against your framework version before installing. If you are on a modern Laravel, budget for a fork or a 30-line replacement using Http::withHeaders()->post().
The endpoint is often plain HTTP. BulkSMSBD's API has historically been served over http://bulksmsbd.net/api/smsapi, which sends your API key and message contents in cleartext. If the config allows an HTTPS base URL, use it; if not, patch the config and verify the gateway accepts TLS.
Number formatting will bite you. The API expects a specific Bangladeshi format — usually 8801XXXXXXXXX rather than 01XXXXXXXXX. Normalize at the boundary: strip spaces and dashes, convert local 01 prefixes to 8801, and reject anything that is not 13 digits after normalization. A silently malformed number returns a generic failure that is easy to misread as a credential problem.
Response parsing is inconsistent. The gateway returns JSON with response_code, success_message, and error_message, but the package may return the raw array, a bool, or a decoded object depending on version. Log the raw response once during integration, then normalize to a single return type in your wrapper. Do not trust a truthy string as success.
Config caching. After changing .env credentials, run php artisan config:clear (or config:cache in deploy). The classic "it works in tinker but not in the app" symptom is a stale cached config reading old credentials.
Sender ID must be approved. A masked sender ID that is not registered against your BulkSMSBD account will fail at the gateway, not in your code. Confirm approval in the BulkSMSBD dashboard before debugging further.
Set timeouts. The package likely uses a default HTTP timeout (or none). Wrap the call or override the client so a hung gateway does not stall your queue worker. 10–15 seconds is reasonable.
Cost and rate limits. Every retry costs money. Cap $tries, and add per-number throttling for OTPs (e.g. one per 60 seconds via RateLimiter) so a stuck job cannot spam a user or burn credit.
Do not log phone numbers or message bodies at info level. They are PII and often contain OTPs. Log the normalized response code and a hash of the number instead.
Extension points are your exit ramp. The useful knobs are the published config (endpoint, credentials, sender ID) and the container binding. If the package's facade is bound as a singleton, you can app()->instance() a decorated version in a service provider to add logging, retries, or normalization without touching vendor code. That decorator is also the cleanest migration path when you eventually replace the package.
How can I help you explore Laravel packages today?