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

Knp Mailjet Bundle Laravel Package

knplabs/knp-mailjet-bundle

Symfony bundle that integrates the Mailjet email delivery service, giving you simple configuration and service access for sending transactional emails via Mailjet's API inside your Symfony application.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the honest framing: knplabs/knp-mailjet-bundle is a Symfony2 bundle — archived since 2016, last tagged release July 2016, 10 stars, zero dependents. You cannot composer require it into a Laravel app and get anything usable: it registers via KnpMailjetBundle::class in a Symfony kernel, ships a DependencyInjection extension, and wires a Swiftmailer transport (Swiftmailer itself is EOL). Treat the repo as reference material, not a dependency.

What to read first (30 minutes, tops):

  1. DependencyInjection/Configuration.php and KnpMailjetExtension.php — this is where api_key, secret_key, and the sandbox call flag are defined. That config shape is the only thing worth copying.
  2. The Swiftmailer transport class (MailjetTransport / MailjetApiTransport) — it's ~100 lines and shows the entire "send one message through Mailjet's API and map the response" flow. That's the reusable idea.
  3. README.md — confirms the two integration modes: SMTP-ish transport vs raw API client.

Minimal steps to get the equivalent working in Laravel:

composer require mailjet/mailjet-apiv3-php
// config/services.php
'mailjet' => [
    'key'    => env('MAILJET_APIKEY'),
    'secret' => env('MAILJET_APISECRET'),
    'sandbox'=> env('MAILJET_SANDBOX', false),
],
// app/Providers/AppServiceProvider.php — bind the client once
$this->app->singleton(\Mailjet\Client::class, fn () => new \Mailjet\Client(
    config('services.mailjet.key'),
    config('services.mailjet.secret'),
    true,
    ['version' => 'v3.1']
));

First use case: a single transactional send — password reset or order confirmation — through API v3.1, verifying in the Mailjet dashboard that the message lands. Do this before touching templates or webhooks; it validates credentials and the sandbox flag in one shot.

If you'd rather not hand-roll it, the official mailjet/laravel-mailjet wrapper is the pragmatic choice, and newer Laravel versions may ship a first-party Mailjet mailer — check config/mail.php in your installed version before adding any package at all.

Implementation Patterns

Pattern 1 — Thin client binding (most common). Bind Mailjet\Client as a singleton (above) and inject it into a small MailjetMailer service. Keeps the vendor SDK out of controllers and gives you one place to swap in a fake for tests. This is the direct translation of what the bundle's DI extension did.

Pattern 2 — Custom Laravel mail transport. If you want Mail::to(...)->send(new OrderShipped($order)) to route through Mailjet's API instead of SMTP, implement Symfony\Component\Mailer\Transport\TransportInterface (Laravel 9+) that calls $client->post(Resources::$Email, ['body' => $payload]), then register it in a service provider:

Mail::extend('mailjet-api', fn (array $config) => new MailjetTransport($client));
// then MAIL_MAILER=mailjet-api

This is the modern equivalent of the bundle's Swiftmailer transport, and it's the pattern worth stealing from the repo — the bundle's transport class is effectively a worked example of the payload shape (From, To, Subject, TextPart, HTMLPart).

Pattern 3 — Templated sends. Mailjet's real leverage is server-side templates. Store the template ID in config and pass variables, rather than building HTML in PHP:

$body = [
    'From' => ['Email' => config('mail.from.address'), 'Name' => config('mail.from.name')],
    'To'   => [['Email' => $user->email, 'Name' => $user->name]],
    'TemplateID' => config('services.mailjet.templates.order_shipped'),
    'TemplateLanguage' => true,
    'Variables' => ['order_ref' => $order->reference, 'tracking' => $order->tracking_url],
];

Note SandboxMode => true in the body when the bundle's call: false equivalent is set — keep that toggle in config, never in a hardcoded branch.

Pattern 4 — Laravel-native integration tips.

  • Wrap sends in a queued Job or Mailable so a slow Mailjet round-trip never blocks a request. The bundle had no queue story; Laravel gives you one for free.
  • Use Http::fake() or mock the bound Client in tests — never hit the live API from a test suite.
  • Log the Mailjet MessageID and Messages[0]->To[0]->MessageID on every send; it's the only way to reconcile delivery later.
  • Fire a domain event (MailSent) from your wrapper so analytics/webhooks aren't bolted on ad hoc.

Gotchas and Tips

Compatibility is a wall, not a speed bump. The bundle's composer.json targets Symfony 2.x and PHP 5.x-era constraints, and its Swiftmailer transport is built against Swift_Transport, which Laravel dropped in favor of Symfony Mailer. Attempting to install it will either fail resolution or drag in abandoned packages. Don't.

HTTP 200 does not mean delivered. Mailjet returns 200 with a body that can contain per-recipient failures. The bundle's transport checked the response status but the API's Messages[].Status is where the truth lives (success, error, queued). Always inspect it and throw on anything but success, or your failures will be silent.

Two different credential pairs. API v3 uses APIKey + APISecret. SMTP relay uses a different username/password pair from the same dashboard. The bundle predates v3.1 and its config naming is easy to conflate with SMTP credentials — keep them in distinct env vars (MAILJET_APIKEY/MAILJET_APISECRET vs MAILJET_SMTP_USER/MAILJET_SMTP_PASS) and never share.

Sandbox toggles silently swallow mail. Mailjet's SandboxMode returns a valid-looking response with a MessageID but delivers nothing. This is the #1 "why isn't mail arriving in staging" call. Gate it strictly on app()->environment() and log loudly when it's on.

Template variables are typed, not strings. A numeric order ID passed as a string renders fine in {{var:order_ref}} but breaks numeric filters and conditionals. Cast at the boundary: (int), (float), (bool). Mailjet will not coerce for you.

Config caching will bite you. If you read config('services.mailjet.*') inside a closure that's resolved from a cached container (php artisan config:cache), stale values survive deploys. Read config inside the singleton closure, not at class-construction top level, and clear cache in your deploy script.

Extension points worth building yourself, since the bundle offers none: a MailjetClientFactory you can swap for a fake in tests, a Transport decorator that adds retry-with-backoff on 429, and a webhook controller for bounce/spam events (the bundle never handled inbound events — that's a gap, not a feature).

Rate limits and batching. Mailjet's send endpoint accepts arrays of messages — batch notifications rather than looping one API call per recipient. The bundle's transport was one-message-per-send by design; don't inherit that bottleneck.

Last word on leverage: the architecture here (DI-configured API client + a transport adapter + a sandbox flag) is genuinely the right shape and translates cleanly to Laravel. The code does not. Budget an hour to read it, zero hours to install it.

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.
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
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums