symfony/brevo-mailer
Brevo (formerly Sendinblue) mailer integration for Symfony Mailer, supporting SMTP and API transports plus custom headers for templates, tags, params, and metadata.
What this package actually is. symfony/brevo-mailer is a Symfony Mailer bridge, not a standalone mail library and not a Laravel package. It contributes two things: a BrevoTransportFactory that teaches Symfony's Transport::fromDsn() two new schemes (brevo+api://, brevo+smtp://), and two thin transports — an HTTP API transport (Brevo /v3/smtp/email over symfony/http-client) and an SMTP transport wrapping EsmtpTransport. Everything else — message composition, MIME handling, events — comes from symfony/mailer, which Laravel already ships.
Architecture fit. Better than the "Symfony package in a Laravel shop" framing suggests. Since Laravel 9, Illuminate\Mail\MailManager is a thin configuration layer over Symfony Mailer and resolves Symfony\Component\Mailer\Transport\TransportInterface instances. That means the bridge's transports are directly droppable into Laravel — the only piece Laravel doesn't consume natively is the DSN factory. You lose the MAILER_DSN=brevo+api://KEY@default ergonomics and gain ~30 lines of service-provider glue. Fit is good, gated on that glue being trivial (it is).
Technical risk. Low on the code, moderate on relevance:
symfony/* version constraints, not packagist popularity. Do not use this number as a quality gate.brevo+smtp:// gives you nothing over Laravel's stock smtp mailer pointed at Brevo's SMTP host. Roughly 80% of the value (deliverability, DKIM/SPF alignment, bounce handling) is achieved with zero new dependencies. The bridge only earns its place if you need the API transport — templateId, params, tags, X-Mailin-Custom, sender.ip.TransportException; Brevo's 400-level template/parameter errors arrive as generic send failures unless you unwrap ResponseInterface from the exception's previous chain. Budget for that.Key questions to resolve before committing:
Mail::extend() glue, or would we rather buy the same capability via Brevo's own PHP SDK?Stack fit and constraints. Expect: PHP 8.1+, symfony/mailer ^6.4|^7.0, symfony/mime, psr/event-dispatcher. Laravel 9/10 sit on Symfony 6 and Laravel 11/12 on Symfony 7, so pin the bridge branch to the Symfony family your Laravel version resolves — a mismatch here is the single most likely Composer failure. Verify with composer why-not symfony/brevo-mailer rather than guessing.
Glue required. Laravel's mailer config is array-based, not DSN-based, so the factory needs a small adapter in a service provider:
Mail::extend('brevo', fn (array $config) => (new BrevoTransportFactory())->create(new Dsn($config['dsn']))).brevo mailer entry to config/mail.php with the DSN assembled from env (keep the key in the secret store / env, never in the config file — the DSN embeds the credential).default on the existing mailer; route specific mailables via Mail::mailer('brevo') so rollback is a config flip, not a deploy.Recommended migration path (sequenced, each step independently shippable):
smtp mailer at Brevo (smtp-relay.brevo.com:587, STARTTLS). Validates domain authentication, deliverability, and bounce routing with zero new packages. This is the step most teams should stop at.brevo+api://, confirm custom headers/tags/templateId land correctly in the Brevo dashboard and webhook events.brevo mailer for the specific mailables that need templates or tagging. Keep transactional receipts/password resets on SMTP until the API path has a month of production history.Port/TLS gotcha to encode in config: port 465 = implicit TLS; any other port starts cleartext and upgrades via STARTTLS only if offered. A misconfigured port can silently downgrade or hard-fail. Pin the port explicitly and assert it in config validation.
Maintenance. Effectively inherited from Symfony — releases track Symfony minors, security fixes arrive with the framework. Your maintenance burden is the glue layer (small) and the Brevo API version (Brevo ships /v3 and occasionally deprecates endpoints; the bridge is thin enough that upstream breakage surfaces as a Composer-bump-and-test, not a refactor). Watch for bridge abandonment: if Brevo rebrands again, the Symfony bridge may lag.
Support. Two vendors, one seam. Symfony-side issues are well-documented and rarely bridge-specific. Brevo-side issues (deliverability, rate limits, template validation) are Brevo's support, and the bridge will not abstract them — you will still be reading Brevo's API reference. Assign a single owner who understands both.
Scaling. The API transport is synchronous over symfony/http-client — throughput is bound by Brevo round-trip latency, not by your app. Mail must go through queues (ShouldQueue mailables, named queues, rate-limited jobs) or a broadcast notification will become a latency incident. Configure queue backoff and maxExceptions deliberately; Symfony converts HTTP failures to TransportException without built-in retry, so retry policy is yours. Confirm your Brevo plan's transactional rate limit and size worker concurrency against it — exceeding it produces 429s that, unretried, become silent mail loss. SMTP scales differently: one connection per transport instance, so persistent workers are fine but per-request sends without queueing are not.
Failure modes to instrument:
templateId plus params couples code to Brevo's template registry; a template edited in the Brevo UI can silently break a mailable with no deploy on your side. Treat template IDs as versioned config and snapshot params in your tests.Ramp-up. Low — an experienced Laravel engineer can complete steps 1 and 2 in under a day. The learning curve is Brevo-side (domain auth, template registry, webhook event semantics), not Symfony-side. Budget a half-day for domain authentication and a day for deliverability validation before any production traffic.
TPM recommendation. Treat this as a conditional adopt, scoped to whether server-side Brevo templates are genuinely required. The default should be step 1 (native SMTP, no new dependency); pull the bridge in only when template rendering, tagging, or X-Mailin-Custom headers become a product requirement. The package itself is low-risk and cheap to remove — the decision to weigh is dependency surface versus template functionality, not code quality.
How can I help you explore Laravel packages today?