Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Mailjet Sms Laravel Package

lab404/laravel-mailjet-sms

View on GitHub
Deep Wiki
Context7

Getting Started

This package adds a Mailjet SMS transport to Laravel in two shapes: a notification channel (MailjetSmsChannel) so SMS rides alongside mail/database in via(), and a direct send facade/client for one-off messages. It is small and single-purpose — expect a config file, a channel, a message object, and a thin HTTP wrapper around Mailjet's v4 SMS endpoint. Nothing about your existing notification flow changes.

Install and configure

composer require lab404/laravel-mailjet-sms
php artisan vendor:publish --tag=mailjet-sms-config   # confirm tag in README
MAILJET_SMS_TOKEN=your-sms-token
MAILJET_SMS_SENDER=MyApp

Two things to sort out before writing code, because they cause the most day-one friction:

  1. The SMS token is not your Mailjet API key/secret. Mailjet SMS is a separate product with its own token, and it must be activated on the account. If SMS isn't enabled, every call returns an auth/forbidden error that looks like a code bug.
  2. The sender is registered/validated on Mailjet's side. Alphanumeric senders work in some countries and are silently rewritten or rejected in others (notably the US/Canada). Pick a short (≤11 char) alphanumeric sender.

Where to look first

  • config/mailjet-sms.php — token, sender, any base URL/endpoint override.
  • The channel class (Lab404\MailjetSms\Channels\MailjetSmsChannel) — this defines the exact method name it looks for on your notification (toMailjetSms() in the typical 404lab convention; verify against the source).
  • The message class — this is what you actually build in toMailjetSms(), so its fluent API is your day-to-day surface.

First use case: order/OTP notification

use Illuminate\Notifications\Notification;
use Lab404\MailjetSms\Channels\MailjetSmsChannel;
use Lab404\MailjetSms\Messages\MailjetSmsMessage;

class OrderShipped extends Notification
{
    public function via($notifiable): array
    {
        return [MailjetSmsChannel::class];
    }

    public function toMailjetSms($notifiable): MailjetSmsMessage
    {
        return (new MailjetSmsMessage())
            ->to($notifiable->phone)          // E.164: +33612345678
            ->content("Order #{$this->order->id} is on its way.");
    }
}

Add the Notifiable trait to your model if it isn't there, and implement routeNotificationForMailjetSms() when the destination phone isn't a simple phone column. That single method is the cleanest integration point for multi-tenant or "notify a different number than the account holder" cases.

Implementation Patterns

Notification channel is the default; direct send is the escape hatch. Put anything triggered by a domain event through Notification — you get queueing, Notification::fake() in tests, and a single via() switch per user preference. Reserve the facade/client for cases where there's no notifiable (health-check pings, admin console "send test SMS", batch scripts).

// Direct send — check the actual facade accessor in the README;
// 404lab packages typically register an alias and a container binding.
MailjetSms::send('+33612345678', 'Deploy finished');

Routing via the notifiable is the pattern that scales. Instead of hardcoding ->to() in every notification, centralize it:

public function routeNotificationForMailjetSms(Notification $notification): ?string
{
    return $this->phone_e164 ?: $this->phone;
}

Return null to skip — but see the gotcha below, since silent skips are easy to miss.

Queue everything. SMS is an external HTTP call inside your request cycle otherwise. Either implement ShouldQueue on the notification or dispatch the notification from a job. Because there's typically no retry logic inside the package, wrap it yourself:

class SendShipmentSms implements ShouldQueue
{
    public int $tries = 3;
    public array $backoff = [10, 60, 300];

    public function handle(): void
    {
        Notification::route('mailjetSms', $this->phone)   // on-demand route
            ->notify(new OrderShipped($this->order));
    }
}

Notification::route() is the cleanest way to notify a number that has no model — good for guest checkout confirmations.

Keep message construction boring. A single message class, .-concatenated short strings, no markdown. If you need templating, render a tiny Blade string first and only then hand it to the message object:

->content(view('sms.order-shipped', ['order' => $this->order])->render())

Testing. Use Laravel's standard fakes — the channel is just another notification channel, so Notification::fake() plus assertSentTo() covers it, and you never hit the network. For the direct-send path, bind a fake into the container or Http::fake() the Mailjet endpoint (the package uses the HTTP client, so the standard fake intercepts it):

Http::fake(['api.mailjet.com/*' => Http::response(['status' => 'success'])]);

Local/staging. Guard with an environment check in via() — return an empty array or a Log channel in local — rather than disabling the token. It keeps the code path exercised and prevents an accidental production SMS bill from a seeder.

Gotchas and Tips

Phone numbers must be E.164. +33612345678, never 06 12 34 56 78, never (555) 010-9999. Mailjet won't normalize for you, and a malformed number often shows up as a silent non-delivery rather than a validation error. Normalize on write (a phone cast/mutator) and validate with phone:AUTO at the boundary.

A null from routeNotificationForMailjetSms() fails silently. The channel typically just returns early. When an SMS "isn't sending," check for a null/blank phone before you touch the config — it's the single most common cause.

No delivery receipts out of the box. Mailjet SMS supports status callbacks, but this package (last release Feb 2023, 5 stars, effectively one maintainer) does not ship webhook handling or a status model. If you need "delivered vs. failed," you're implementing a callback endpoint and a status column yourself, and parsing Mailjet's payload. Plan for that before you promise anyone delivery analytics.

Content length and encoding. GSM-7 gets ~160 chars/segment; a single curly quote, emoji, or accented character flips the whole message to UCS-2 at ~70 chars/segment, and a 161-character GSM message costs two SMS. Budget content length explicitly and strip smart quotes from rendered Blade.

Config caching. After changing .env in a deployed environment, php artisan config:cache again. MAILJET_SMS_TOKEN read via env() outside a config file returns null once cached — make sure the package config actually reads it (it should) rather than the package calling env() directly.

Errors are not retried and often surface as generic exceptions. Wrap sends in try/catch or a queued job with $tries/$backoff, and log the response body. Mailjet returns useful detail (invalid sender, insufficient credit, blocked country) that's easy to lose behind a generic RequestException.

Laravel version constraints. With a 2023 release and essentially no dependents, check composer.json for the supported Laravel range before upgrading. On newer Laravel you may need --with-all-dependencies or a fork. Vendor-publish and pin, or fork it into your packages/ directory — the surface is small enough to own.

Extension points, in order of usefulness:

  • Container binding — if the client is bound as a singleton, swap it for a decorator that adds logging, metrics, or a dry-run mode that logs instead of sending. Best single lever.
  • Custom channel — subclass the channel if you need per-tenant tokens; resolve the sender/token per notifiable rather than globally.
  • Message class subclass — add ->sender() overrides or a ->template() helper without forking the package.
  • HTTP client middleware — since it uses Laravel's HTTP client, a global Http::macro or middleware handles retries/timeouts more cleanly than editing vendor code.

Sanity-check the README before committing. With a package this small and this stale, method names (toMailjetSms vs toSms, the facade alias, the publish tag) are worth confirming against the source once rather than trusting a blog post. Read the channel class — it's ~30 lines and tells you the entire contract.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator