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 Brevo Notifier Laravel Package

yieldstudio/laravel-brevo-notifier

Send Laravel notifications through Brevo (formerly Sendinblue) with this dedicated notifier package, letting you deliver transactional emails and SMS messages to your users via a simple, familiar notification channel.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the channel and publish its config:

composer require yieldstudio/laravel-brevo-notifier
php artisan vendor:publish --tag=brevo-notifier-config

Add the credential to .env (and to your deployment secret store — this package is useless without it):

BREVO_API_KEY=xkeysib-...

First use case — a transactional email from a notification:

use YieldStudio\LaravelBrevoNotifier\BrevoMessage;
use YieldStudio\LaravelBrevoNotifier\BrevoChannel;

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

    public function toBrevo($notifiable): BrevoMessage
    {
        return BrevoMessage::create()
            ->subject('Welcome aboard')
            ->html('<p>Glad you are here.</p>');
    }
}

The address is resolved from $notifiable->routeNotificationFor('brevo'), falling back to email — so a standard User model works with no extra wiring. Send it with $user->notify(new WelcomeNotification()).

Where to look first: the BrevoMessage class is the whole API surface — read its setters before reaching for the Brevo REST docs, since the package maps almost every transactional-email field. Then check config/brevo-notifier.php for the API key, endpoint/base URL and sender defaults.

Implementation Patterns

Template-driven messages. For anything marketing or content-managed, prefer Brevo's stored templates over inlined HTML so copy changes don't require a deploy:

return BrevoMessage::create()
    ->templateId(42)
    ->params(['first_name' => $notifiable->first_name, 'cta_url' => $url]);

Tags for segmentation and observability (new in 1.0.3). Tags are the cheapest way to make the Brevo dashboard usable — filter statistics, debug which notification produced a bounce, and give support a keyword to search on. Adopt a convention early (notification:welcome, env:staging, team:billing) because retrofitting tags onto historical sends is impossible:

return BrevoMessage::create()
    ->subject('Your invoice is ready')
    ->html($html)
    ->tags(['notification:invoice', 'env:'.app()->environment()]);

Tag per notification class, not per user — user-level identifiers belong in params, not in tags, which are low-cardinality by design.

Use the returned API response (new in 1.0.3). The channel now hands back the decoded Brevo response instead of null, so you can capture the messageId for correlation and support tickets:

$responses = $user->notify(new InvoiceReadyNotification($invoice));

$messageId = $responses[BrevoChannel::class]['messageId'] ?? null;

Log::info('brevo.sent', ['message_id' => $messageId, 'user_id' => $user->id]);

Treat the shape as Brevo's, not the package's — it varies between the email and SMS endpoints and across API versions. Read defensively (?? null), never destructure it positionally, and don't persist the whole payload unless you actually need it.

Queue by default. The channel is synchronous; an outbound HTTP call inside a web request is a latency and availability risk. Implement ShouldQueue on every notification, and set tries/backoff on the notification or a dedicated queue so a Brevo 5xx doesn't burn your default worker retries.

Keep payload construction in the notification, transport concerns in the channel. Build BrevoMessage in toBrevo(), and put things like a dynamic sender, reply-to, or BCC into a small base notification class your app extends. Don't wrap BrevoChannel in a custom channel just to add headers — extend BrevoMessage instead if you need a setter the package lacks.

Testing. Assert the outbound payload with Http::fake() rather than mocking the channel, so you catch serialization regressions (and tag typos):

Http::fake(['api.brevo.com/*' => Http::response(['messageId' => '<abc@brevo>'], 201)]);

$user->notify(new InvoiceReadyNotification($invoice));

Http::assertSent(fn ($request) =>
    $request['tags'] === ['notification:invoice', 'env:testing']
    && $request['to'][0]['email'] === $user->email
);

Notification::fake() is still the right tool for asserting that a notification was sent; the HTTP fake is for asserting what was sent.

Gotchas and Tips

templateId and content are mutually exclusive. Setting both a template and html/text produces a Brevo 400, not a silent preference. Pick one per notification and keep it consistent.

Sender must be verified in Brevo. Unverified sender domains fail at send time with a 400 that reads nothing like an auth problem. Configure the default sender once in config/brevo-notifier.php and override only for multi-brand apps. replyTo is not the sender and won't satisfy verification.

The response is not a success contract. A 2xx means Brevo accepted the message, not that it was delivered. Don't mark an order as "customer notified" off the returned messageId — use Brevo webhooks for delivery/bounce events if that distinction matters.

Returned responses change error semantics. Exceptions still surface for non-2xx, but code that previously did if ($user->notify(...) === null) or ignored the return will now see an array. Anything that type-hinted or compared against null needs a look on upgrade — the bump is patch-level, but the behavioural change is real.

Tags have platform limits. Brevo caps the number of tags per message and restricts characters; long or space-separated tags get rejected or silently mangle your dashboard filters. Stick to short, lowercase, colon-delimited tokens and assert them in a test so a refactor doesn't quietly break your analytics.

Config caching bites during local setup. After changing the API key or sender in .env, run php artisan config:clear — a stale cached config produces confusing auth failures against the wrong Brevo account.

No built-in retry or timeout. The underlying HTTP call uses defaults; wrap sends in retry() or rely on queue retries, and consider setting an explicit timeout if you're not queueing during a migration period. Failures throw — make sure your job's failed() handler doesn't lose the notification silently.

Pin the version in composer.json at ^1.0.3 if you start relying on tags or the returned response. Both are additive, but the earlier 1.0.x line returns null and will break code written against the new behaviour.

Extension point: if you need fields the package doesn't expose, subclass BrevoMessage and add your own toArray() merge rather than forking — the channel only cares that toBrevo() returns a message instance.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin