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

jeffersongoncalves/laravel-brevo

A Laravel package for integrating Brevo (formerly Sendinblue) into your app, making it easy to send transactional email and work with Brevo’s API from a familiar Laravel setup.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a fresh 1.0.0, so treat it as a thin, unproven wrapper: the value is that it saves you hand-rolling Guzzle calls and config plumbing, not that it hides the Brevo API from you.

Install and configure

  1. composer require the package, then check whether it auto-discovers a service provider (Laravel 5.5+ convention). If not, register the provider and any facade alias manually in config/app.php.
  2. Publish the config: php artisan vendor:publish --tag=brevo-config (tag name may differ — run php artisan vendor:publish and pick the provider). This is the first place to look: the config file enumerates which features are wired up (mail transport? SMS? webhooks?) and what env keys it expects.
  3. Add credentials to .env. Expect something like BREVO_API_KEY= — an API v3 key from Brevo → SMTP & API → API Keys. Note the key is scoped; a v3 key is not the same as the SMTP password or a v2 key.
  4. php artisan config:clear if you had a cached config before adding the env var. This trips up more people than anything else on day one.

Where to look first

  • The config file (above).
  • The facade / root class — its docblock @method annotations or interface is the de facto API listing. In a 1.0.0 with sparse docs, php -r a ReflectionClass dump or just lean on IDE autocompletion rather than guessing method names.
  • README for the exception class(es) thrown on non-2xx responses and how to get the raw response body. You will need that within the first hour — Brevo's 400s carry a useful code/message payload.

First use case: transactional email

The highest-value, lowest-risk entry point. Either:

  • Mail transport route — if 1.0.0 registers a Laravel mailer driver, set MAIL_MAILER=brevo and keep using Mail::to($user)->send(new WelcomeEmail()). Your existing Mailables, Blade views and Mail::fake() tests keep working. Start here if it exists.
  • Direct API route — call the facade to send with a Brevo template ID and params, or with inline htmlContent/subject. Use this when you need Brevo-side templates that marketing can edit without a deploy.

Resist starting with campaigns. Transactional email is the well-trodden path; campaign sending has more account-level prerequisites (verified sender, opt-in proof, possible account review).

Implementation Patterns

Wrap it, don't sprinkle it. Even in a small app, put a thin App\Services\BrevoNotifier (or similar) in front of the package. Every call site then depends on your interface, not on an unaudited 1.0.0 package — which matters because a 1.0.0 can churn. It also gives you one place to add logging, retries and rate limiting.

Transactional email — two patterns, pick deliberately

  • Templated: pass templateId plus a params array of merge fields. Brevo renders the HTML. Pros: content changes don't need a deploy; non-developers can iterate; consistent branding. Cons: your email content lives outside version control and isn't covered by your test suite.
  • Inline Mailable: render Blade locally, send htmlContent/textContent. Pros: previewable, testable, in git. Cons: any copy change requires a deploy.

A common split: transactional/security mail inline (you want it in git and reviewed), lifecycle/marketing-ish mail via templates.

Contacts and lists — sync, don't create

The bread-and-butter integration is "user signed up → contact exists with the right attributes and is on the right list." Model this as an idempotent upsert, not create-then-check:

  • Upsert the contact with email as the key, passing attributes in one payload.
  • Add to list(s) in the same flow, or rely on Brevo automation triggered by the list add.
  • Store the Brevo contact ID on your users table (brevo_contact_id) if you'll do updates later, though email is a fine natural key and avoids staleness when a user changes address.

Drive this from a domain event + queued listener so signup isn't blocked on an HTTP round trip.

Campaigns

Model campaigns as a two-step, human-gated process: create the campaign (subject, sender, list/segment, template) in a draft state, let someone review it in the Brevo UI, then send or schedule. Do not wire "create and send" straight to a controller action — a duplicate form submission is a duplicate send to your whole list. Use schedule with an explicit UTC timestamp rather than "send now" where possible, so a mistake is cancellable.

Senders and account bootstrap

Treat sender/domain verification as a one-time setup task, not a runtime concern. Verify the sender and set up DKIM/SPF/DMARC on the domain before you write a line of send code — otherwise you'll spend an afternoon debugging a package that's working fine and a domain that isn't. A small artisan command or admin screen that lists verified senders is a worthwhile early investment.

Queue everything outbound

Every send/list-sync should be on a queue with $tries, $backoff and ShouldBeUnique where duplicate delivery is harmful. This also decouples you from Brevo latency and lets you apply a rate limiter.

Testing

  • Http::fake() against the package's underlying client — assert the outbound URL/method/body shape. This is your real safety net against a 1.0.x changing request formats.
  • Mail::fake() will not catch transport-level problems if you're using the mail driver; it intercepts before the transport. Pair it with an Http::fake() test for the transport path.
  • Keep one integration test against a sandbox/test list, gated behind an env flag and excluded from CI.

Gotchas and Tips

Thin docs at 1.0.0. Verify method names against the facade source rather than blog posts (which will mostly describe the older Sendinblue PHP SDK). If the package doesn't expose what you need, don't fight it — the underlying Brevo\Client SDK (or Http::withHeaders(['api-key' => ...]) against https://api.brevo.com/v3) is a legitimate escape hatch, ideally behind your service wrapper.

Brevo-side quirks that look like package bugs

  • Attribute names are case-sensitive and Brevo normalises them (typically uppercase). Sending first_name and later reading FIRST_NAME is a classic silent-no-op. Settle on one convention early and check the contact in the UI, not just the 201 response.
  • Create-vs-update semantics on contacts. Depending on the endpoint, a duplicate email may return 400 rather than updating. Use the dedicated create-or-update path; don't code against the "create" response being idempotent.
  • List IDs are numeric and environment-specific. Hardcoding listId => 3 is fine in dev and wrong in prod. Put list IDs in config keyed by audience name.
  • sender must be a verified sender. Sending from an arbitrary From: address fails at the API, not at your code.
  • Template vs inline content are mutually exclusive; supplying both (or the wrong one for the endpoint) returns an unhelpful 400.
  • Scheduling timezone. Scheduled times are generally taken as UTC — if you accept a local time, convert explicitly and write a test for it. Off-by-hours scheduled campaigns are a recurring, embarrassing bug.

Error handling. Expect an exception on non-2xx. Catch the package's exception type, and — critically — confirm you can retrieve the raw response body. Brevo returns structured errors (code, message) that tell you exactly which field is wrong; losing that in a generic RuntimeException will cost you hours. Log the body, never the API key.

Rate limits and bulk operations. Brevo enforces per-account request limits, and contact import/listing endpoints paginate (commonly limit capped around 100 with offset). A naive "loop all contacts" in a request will time out; use a chunked job with usleep/a rate limiter, and expect throttling to be a normal condition rather than an error.

Unsubscribe and compliance. Don't rely solely on Brevo's unsubscribe link in templates — if you send inline htmlContent you may still need it. And keep your users.newsletter flag and the Brevo list in sync bidirectionally via webhooks, or you'll email people who opted out.

Webhooks. The package covering "contacts/lists" doesn't guarantee webhook signature verification is built in. If you consume Brevo events (bounces, unsubscribes, delivered), validate and make your handler idempotent — retries and out-of-order delivery are normal.

Config caching. BREVO_API_KEY read at container-build time plus config:cache in deploy scripts equals "works locally, 401 in production." Add php artisan config:cache to your deploy, not your local workflow.

Multi-tenant / multi-brand. If you'll send from more than one Brevo account, don't lean on the singleton binding — check whether the package supports per-call credential overrides, or instantiate the client yourself per tenant. Retrofitting this is painful.

Version pin. Pin with a caret (^1.0) but review 1.x minor releases before deploying: for a wrapper this young, request-shape changes are the most likely breaking surface, and they may not be called out as "breaking" in semver terms.

Sandbox/test mode. Brevo has limited sandbox capability (notably for transactional SMS and some email paths). Structure your integration tests to assert on request shape against Http::fake() and reserve live calls for a manual smoke script — don't block CI on an external vendor's availability.

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.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel