welp/mailjet-bundle
Symfony bundle for integrating the Mailjet email API, providing configurable transport, event handling, and Laravel-style mailer support.
Reality check before you install anything. welp/mailjet-bundle is a Symfony bundle for the Mailjet Send API v3, last released in June 2019. It is not a Laravel package: it has no service provider, no facade, no config publish tag, and it is wired through Symfony's DI container and bundle extension. In a Laravel app you should not install it — you get the same functionality in about 15 lines by binding the underlying SDK (mailjet/mailjet-apiv3-php) as a singleton. Treat this bundle as a reference implementation and only install it if you are actually on Symfony 3.4/4.x.
Where to look first in the source:
DependencyInjection/Configuration.php — the real config keys (api_key/secret_key naming varies by tag; older docs say public_key/private_key).DependencyInjection/*Extension.php and Resources/config/services.* — the service IDs and whether the client service is public or private.composer.json — check the symfony/* and PHP constraints before wasting time; there is no Flex recipe.Symfony path (the package's intended use):
composer require welp/mailjet-bundle mailjet/mailjet-apiv3-php
# register manually in config/bundles.php if there is no recipe
bin/console config:dump-reference welp_mailjet # confirm key names for your tag
bin/console debug:container --show-private | grep -i mailjet
# config/packages/welp_mailjet.yaml
welp_mailjet:
api_key: '%env(MAILJET_API_KEY)%'
secret_key: '%env(MAILJET_SECRET_KEY)%'
sandbox: false
Then inject the bundle's client/transport service into one small WelcomeMailer-style class rather than calling the container.
Laravel path (recommended): skip the bundle. Install the SDK and bind it once.
composer require mailjet/mailjet-apiv3-php
// app/Providers/MailjetServiceProvider.php
public function register(): void
{
$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: one transactional email (welcome or password reset) in Mailjet sandbox mode, asserting the response object, before wiring templates or queues.
Wrap the client, don't scatter payload arrays. The bundle is thin; its value is DI wiring, not abstractions. Put payload construction in one place (a MailjetNotifier or per-notification classes) and keep controllers/jobs ignorant of Mailjet's array shape.
$response = $this->mailjet->post(Resources::EMAIL_SEND, [
'body' => ['Messages' => [[
'From' => ['Email' => '[email protected]', 'Name' => 'Acme'],
'To' => [['Email' => $user->email, 'Name' => $user->name]],
'Subject' => 'Welcome',
'HTMLPart' => view('emails.welcome', compact('user'))->render(),
'TextPart' => 'Welcome!',
'CustomID' => 'welcome:'.$user->id,
]]],
]);
if (! $response->success()) {
throw new RuntimeException($response->getReasonPhrase());
}
Templated sends. For anything designed in Mailjet's editor, send TemplateID + TemplateLanguage => true + a Variables map instead of HTML. This is the pattern that makes the bundle worth having a wrapper for: your app emits domain data, Mailjet owns markup.
Batch and queue. Send API v3.1 accepts a Messages array (up to 50 per call). For anything user-triggered, dispatch a queued job (ShouldQueue in Laravel, Messenger in Symfony) and chunk recipient lists. Pass CustomID on every message so you can correlate the Mailjet event log and webhooks back to a row in your database.
Sandbox in dev/test. Either the bundle's config flag (if your tag has one) or per-request 'SandboxMode' => true in the body. It still validates the payload and returns 200, so it is the right way to test payload shape in CI — but it never delivers mail, so do not rely on it to catch template/variable errors.
Transport integration. Older tags ship a Swiftmailer transport so swiftmailer.mailer.default.transport can route through Mailjet. That is only relevant on Symfony 3.4/4.x with Swiftmailer (EOL). On modern Symfony use symfony/mailer with the Mailjet bridge if available, or bypass mailer abstractions and call the client directly. In Laravel, calling the client directly from a queued job is simpler and more debuggable than writing a custom Mail transport.
Webhooks/events are yours to build. The bundle does not handle Mailjet's event webhooks (opens, bounces, spam, blocked). Add a route, verify the request, and push processing onto a queue; store MessageID/MessageUUID at send time so you can join.
Testing. Mock the wrapper, not Mailjet\Client. Assert the array you would post; use sandbox for one integration test. The SDK returns a Mailjet\Response, so success(), getStatus(), getReasonPhrase(), and getData() are your seams.
composer.json constraints before adding it; expect conflicts with symfony/mailer and modern PHP. Forking or copying the ~4 files of DI wiring is usually cheaper than negotiating constraints.FromEmail/Recipients; v3.1 uses Messages/To/From. Which one you get depends on the Resources::EMAIL_SEND constant and the client's version setting in the installed SDK — verify in vendor rather than trusting the README. Mailjet's current guidance is v3.1.api_key/secret_key versus public_key/private_key changed across tags (and the older mailjet/mailjetBundle lineage). Always run config:dump-reference and debug:config against the exact version you installed.$container->get('...') on the bundle's service will throw. Inject it, or add an alias to Mailjet\Client in your own services.yaml so autowiring works.success(), not just HTTP 200. Mailjet returns structured error bodies in getData()['ErrorMessage']; log the full body. A 400 usually means a single malformed recipient or template variable, and the message text points at it.CustomID plus the returned MessageID/MessageUUID for every send. Mailjet's dashboard event log and webhooks are much harder to use without them.vendor/ and don't expect the bundle to grow features.How can I help you explore Laravel packages today?