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

Mailjet Bundle Laravel Package

mailjet/mailjet-bundle

Mailjet Bundle integrates the Mailjet email service into Symfony, providing a configured API client, mailer transport, and services for sending transactional email and managing contacts, templates, and campaigns through Symfony's dependency injection and configuration system.

View on GitHub
Deep Wiki
Context7

Getting Started

First, internalize the mismatch. mailjet/mailjet-bundle is a Symfony bundle. In a Laravel app there is no service provider, no facade, no php artisan vendor:publish, and no auto-discovery. composer require mailjet/mailjet-bundle will install wiring that Laravel's container never calls. The only legitimate reasons to pull it into a Laravel project are (a) you're porting a Symfony app that already depends on it, or (b) you want to copy its ~40 lines of service wiring into your own provider. For greenfield work, start from mailjet/mailjet-apiv3-php (the SDK it wraps) instead.

Where to look first in the source. Read two files and you have the whole contract:

  • DependencyInjection/Configuration.php — the config tree, keys, and defaults.
  • Resources/config/services.xml — the service definitions.

Everything else is delegation to the SDK. The bundle exposes two useful services:

Service ID Class Purpose
mailjet.apiv3 Mailjet\Client Raw API v3 client — the piece you actually want
mailjet.transport Mail/Swift transport Plugs into Symfony's mailer so $mailer->send() routes through Mailjet

The config surface (Symfony-only) is small:

# config/packages/mailjet.yaml — Symfony, not Laravel
mailjet:
    api_key: '%env(MAILJET_APIKEY)%'
    secret_key: '%env(MAILJET_SECRET)%'
    call: true   # true = HTTPS, false = plain HTTP

In Laravel, reproduce those keys under config/services.php and read them with env().

Minimal Laravel bootstrap (the realistic first step):

composer require mailjet/mailjet-apiv3-php
// config/services.php
'mailjet' => [
    'api_key'    => env('MAILJET_APIKEY'),
    'secret_key' => env('MAILJET_SECRET'),
    'call'       => true,
],
// app/Providers/MailjetServiceProvider.php
public function register(): void
{
    $this->app->singleton(\Mailjet\Client::class, fn () => new \Mailjet\Client(
        config('services.mailjet.api_key'),
        config('services.mailjet.secret_key'),
        true,                                  // call = https
        ['version' => 'v3.1']                  // keep the version explicit
    ));
}

First use case — send one transactional email through the API, not SMTP:

$response = $client->post(\Mailjet\Resources::$Email, ['body' => [
    'From'     => ['Email' => '[email protected]', 'Name' => 'App'],
    'To'       => [['Email' => '[email protected]', 'Name' => 'User']],
    'Subject'  => 'Welcome',
    'TextPart' => 'Thanks for signing up.',
]]);

if (! $response->success()) {
    // The client does not throw — always inspect the response.
    logger()->error('Mailjet send failed', $response->getData());
}

That's the day-to-day loop: resolve Mailjet\Client, call post() against a resource, check success(), read getData() on failure.

Implementation Patterns

Pattern 1 — Port the wiring into a Laravel service provider

Treat the bundle as a spec, not a dependency. The bundle's total value is a few lines of container config; reproduce them natively:

  • Bind Mailjet\Client as a singleton so you don't create a new client (and new Guzzle handler stack) per send.
  • Inject it via constructor into a thin MailjetMailer class rather than resolving it from the container inside domain code — this keeps the ESP swappable behind your own interface.
  • Keep credentials in config/services.php reading from env(), exactly as you'd configure any other Laravel service.

Pattern 2 — Hide the SDK behind your own interface

Because the bundle is unmaintained, don't let Mailjet\Client leak into controllers or jobs. Define:

interface TransactionalMailer
{
    public function send(EmailMessage $message): SendResult;
}

Implement it with the Mailjet client; unit-test it against a fake. When Mailjet is replaced (SES, Postmark, Resend), only the implementation changes. This is the single highest-value pattern — the SDK is fine, but it's a detail.

Pattern 3 — Route through Laravel's Mail facade with a Symfony Mailer transport

If you want Mail::to(...)->send(...), queueing, and event hooks to keep working while the transport is Mailjet, implement a Symfony\Component\Mailer\Transport\AbstractTransport subclass and register it via Mail::extend() or config/mail.php. This is the Laravel-native equivalent of mailjet.transport in the Symfony bundle and preserves every call site.

Pattern 4 — Queue transactional sends

Never send synchronously in a request path. Dispatch a queued job that resolves the mailer and calls send(). The client is stateless per request, so there's no batching benefit to holding it; queue it for retry and backoff instead.

SendTransactionalEmail::dispatch($message)->onQueue('mail');

Handle retries with $tries and backoff(), but classify failures first: 4xx (auth, invalid sender, suppressed recipient) are permanent — fail fast. 5xx and timeouts are transient — retry with jitter.

Pattern 5 — Use the client for more than sending

The same Mailjet\Client handles Mailjet's other V3 resources: template fetches (\Mailjet\Resources::$Template), contact list sync (\Mailjet\Resources::$Contactslist), and campaign resources. One DI binding covers all of them — this is where the bundle's mailjet.apiv3 abstraction is actually worth understanding.

Pattern 6 — Batch sends in chunks

Mailjet caps recipients per request. Chunk larger batches and map per-recipient errors from the response rather than treating the batch as all-or-nothing. The response structure exposes per-message results; parse them.

Pattern 7 — Env-based credentials per environment

The bundle's stated "environment-based credentials" is just %env() in Symfony and env() in Laravel. Keep keys in your secret store, never in committed config/. A Mailjet sandbox account per non-prod environment is the honest way to test without polluting your sending reputation.

Gotchas and Tips

The client does not throw on API errors. Mailjet\Client::post() returns a response object; success() is the only way to know. Forgetting this produces silent failures that look like dropped mail. Wrap every call site in an explicit success() check and log getData() on failure.

The bundle will almost certainly fail composer require on modern Laravel. It targets PHP 7.x / Symfony 3–4 and Swiftmailer-era transports. Composer will refuse to resolve against illuminate/* 10/11. Don't burn a sprint on this — document it and move on. This is the concrete reason the "port to a service provider" pattern is the realistic path.

Watch the call flag. true is HTTPS, false is plain HTTP. This is a footgun in config templates — a misread flag sends API keys over the wire. Default it to true and validate it in config.

Pin the API version explicitly. new Client($key, $secret, true, ['version' => 'v3.1']) — don't rely on an implicit default. Mailjet has moved defaults before; being explicit prevents surprise behavior changes.

Match Guzzle majors. The SDK depends on Guzzle; Laravel already pins a version. Verify they agree in composer.json before assuming clean install. A Guzzle major mismatch is the most common integration failure after the framework mismatch itself.

Rate limits and 429s are Mailjet-side, not code-side. Respect daily send quotas, per-request recipient caps, and 429 responses with exponential backoff and jitter. The bundle offers no throttle; you own it.

Idempotency is your job. Mailjet has no de-dup for duplicate sends. Generate your own message keys and use them to prevent double-sends on retry — the client won't do this for you.

Webhooks need signature verification and fast 2xx. If you ingest bounce/spam/open/click webhooks, verify signatures, respond 2xx immediately, process async. Mailjet disables endpoints that time out or return errors — a common way teams lose their event stream silently.

Debug by inspecting raw responses, not stack traces. Because errors are data, not exceptions, use $response->getStatus(), $response->getData(), and $response->getReasonPhrase() when things fail. This is the fastest diagnostic path.

Extension points are in the SDK, not the bundle. Retry hooks, custom Guzzle handlers, logging middleware — all live in mailjet/mailjet-apiv3-php. Anything you need to customize beyond DI wiring goes there, not into the bundle.

Treat the bundle as a reference, not a dependency. Last release 2019, 33 stars, 0 dependents. There is no patch path and no issue triage. The correct long-term posture is: read it for the wiring pattern, build your own adapter, and keep the ESP swappable behind an interface. If you need an actively maintained Mailjet integration for Laravel, evaluate mailjet/laravel-mailjet or a native Symfony Mailer transport instead — this bundle is a Symfony-era artifact whose day-to-day value in a Laravel codebase is limited to its source as documentation.

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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource