laravel-notification-channels/gammu
A Laravel notification channel for sending SMS messages through Gammu, the open-source SMS gateway. Integrate Gammu into your Laravel app and deliver notifications as text messages using Laravel’s familiar notification API.
This package is thin glue between Laravel's notification system and a running Gammu SMSD daemon. It does not talk to a modem itself—Gammu does—so your first step is confirming you have an SMSD database backend (usually MySQL/MariaDB with outbox, sentitems, inbox tables) and a modem/SIM registered and sending.
src/GammuChannel.php first. The channel is small; the one thing that determines your whole integration is whether it inserts a row into SMSD's outbox table or shells out to gammu-smsd-inject. Everything else (config keys, DB grants, testing strategy) follows from that.illuminate/* constraints, so composer require laravel-notification-channels/gammu will likely fail on Laravel 8+. Either fork and widen the constraints, or copy src/ into app/Support/Gammu and keep the NotificationChannels\Gammu namespace. The channel itself is a few dozen lines; don't fight Composer for it.NotificationChannels\Gammu\GammuServiceProvider::class to config/app.php, then publish the config (vendor:publish --tag=gammu) and point it at your SMSD database or binary. Put credentials in .env, never in the published config.php artisan make:notification SendSms, return [GammuChannel::class] from via(), and implement toGammu():use NotificationChannels\Gammu\GammuChannel;
use NotificationChannels\Gammu\GammuMessage;
public function via($notifiable)
{
return [GammuChannel::class];
}
public function toGammu($notifiable)
{
return GammuMessage::create()
->to($notifiable->phone_number) // E.164, e.g. +31612345678
->content('Your code is 123456');
}
Trigger it with $user->notify(new SendSms()) and verify a row appears in outbox and then sentitems. If nothing appears, the problem is your config/DB grant, not Laravel.
Centralize routing on the notifiable. Implement routeNotificationForGammu() returning the phone number. Then notifications don't need ->to() and you can use anonymous routing for non-users:
public function routeNotificationForGammu()
{
return $this->phone_number;
}
Notification::route('gammu', $number)->notify(new SendSms('...'));
Always queue SMS notifications. The channel's work is a fast DB insert, but the modem drains at roughly 6–10 messages/minute. Implement ShouldQueue, put them on a dedicated queue, and rate-limit the job rather than the request:
class SendSms extends Notification implements ShouldQueue
{
public $tries = 3;
public $backoff = [10, 60, 300];
public function middleware()
{
return [new RateLimited(Limit::perMinute(10)->by('gammu'))];
}
}
Build a small base notification/trait for number normalization, 160-character (or 70 for UCS-2) validation, localization via __(), and a consistent content(). Keeping message construction in one place is worth more than any feature the package offers.
Treat delivery tracking as your job, not the package's. The package hands a message to Gammu and stops. Build a read-only connection to the SMSD database and a scheduled command that reconciles outbox/sentitems rows by ID or CreatorID into your own audit table. Do not let the app's primary DB user touch SMSD tables.
Test at two levels. Use Notification::fake() and assertSentTo($user, SendSms::class, fn ($n) => $n->toGammu($user)->content === '...') for notification logic. Add one integration test against a test schema containing the real outbox column set, because Notification::fake() hides SQL and schema errors entirely.
Integration tip: if you need SenderID, DeliveryReport, Priority, SendAfter, or multipart control, check whether GammuMessage exposes it. In this package's era it usually does not—fork the message class or write a 30-line custom channel. The integration surface is one INSERT; owning it is often cheaper than owning an abandoned dependency.
via(object $notifiable): array and PHP 8 signatures yourself.routeNotificationForGammu() returns null or empty, most channels in this family simply return. Your test suite passes, nothing is sent, and nobody notices. Guard and log missing numbers.0612345678 will fail or route incorrectly; +31... vs 31... must be consistent with your SMSD configuration.Coding column must match the text. Accented characters and emoji require UCS-2, which drops the segment size from 160 to 70 and may require MultiPart. Validate length and encoding before insert, not after a failed send.gammu-smsd process all look identical from Laravel: rows accumulating in outbox. Add a health check on the daemon and alert on outbox backlog or rows stuck in Reserved/Sending.SendBefore/SendAfter and SendingTimeOut. Messages can silently expire if the daemon is down past the window. Check the defaults for your version.INSERT on outbox (and SELECT on sentitems if you track delivery). Never reuse the SMSD user that performs deletes and status updates.config:cache is fine, but make sure the Gammu connection is defined as a separate entry in config/database.php with env-driven credentials—otherwise config caching in CI/production can surprise you.GammuChannel for custom transport behavior, subclass or replace GammuMessage for extra columns, and hook Laravel's notification events for metrics. If you find yourself extending heavily, the honest answer is that a first-party channel writing to outbox is a half-day of work and removes the maintenance question entirely.How can I help you explore Laravel packages today?