thedoctor0/laravel-mailjet-driver
thedoctor0/laravel-mailjet-driver provides a Mailjet transport for Laravel's native mail system, letting you send email through Mailjet's API via the Mail facade. Configure credentials and use familiar Laravel mail workflows with minimal setup.
This is a mail transport for Laravel plus a thin wrapper around Mailjet's Send API. Use it when you want Mailjet as MAIL_MAILER for ordinary Laravel mailables, with the option to bypass the Mail facade for payloads it can't express.
Check your constraints first — 2.1.x supports Laravel 11–13 on PHP 8.2+. On Laravel 9–10 you must stay on the 1.x line; treat the Laravel upgrade as a prerequisite, not an afterthought.
Install and publish config:
composer require thedoctor0/laravel-mailjet-driver:^2.1
php artisan vendor:publish --tag=mailjet
Put credentials in .env (MAILJET_APIKEY / MAILJET_APISECRET) and point Laravel at the transport:
MAIL_MAILER=mailjet
Smoke-test both paths immediately, because they fail differently:
// 1) Standard Laravel mail path
Mail::raw('ping', fn ($m) => $m->to('[email protected]')->subject('ping'));
// 2) Direct Send API path (new in 2.1)
$response = Mailjet::sendEmail([
'From' => ['Email' => '[email protected]', 'Name' => 'App'],
'To' => [['Email' => '[email protected]']],
'Subject' => 'ping',
'TextPart' => 'ping',
]);
Then dump $response->getData() — if it contains a Messages[0].Errors array, your credentials or sender are wrong even though nothing threw.
Where to look first: the published config/mailjet.php (key names and transport wiring), then the package's Mailjet entry point for sendEmail() / sendSms(). First real use case is almost always "send transactional mail through Mailjet templates" — a Mailable for simple notifications, sendEmail() with TemplateID + Variables for branded templates.
Mailable-first for anything a human writes. The driver means Mail::to($user)->send(new WelcomeEmail($user)) works unchanged. Keep markdown/Blade mailables on this path — you get Laravel's testing helpers (Mail::fake()), localization, and queueing for free.
Reach for sendEmail() when the Mail facade is lossy. That covers:
TemplateID, TemplateLanguage => true, Variables.SandboxMode, DeduplicateCampaign, CustomCampaign, TrackOpens, Headers.Messages array, not N queue jobs.SentAt) — Mailjet queues it; don't hold a worker open.Isolate the call in one place. Build a MailjetGateway/service class wrapping the facade, with sendTransactional(), sendTemplate(), sendSms(). Everything downstream depends on Mailjet's static API otherwise, and that makes it untestable and impossible to swap. Inject this service; keep the facade at the boundary.
Keep the v3.0 fallback behind a config flag. 2.1 defaults to the v3.1 payload shape and lets you request the v3 shape. Use the fallback only for payloads written before the upgrade (or a Mailjet endpoint that lags), and record why — v3.1 and v3.0 mix Recipients vs To/Cc, and a hybrid payload silently drops recipients instead of erroring.
Queue and retry at the job level. Send from queued jobs with $tries/backoff() rather than retrying inline. Mailjet returns HTTP 200 with per-message errors, so retries based on HTTP status alone will not fire — inspect the body, classify (invalid address = permanent, rate limit/throttle = retryable), and rethrow only the latter.
SMS lives beside mail, not through it. Mailjet::sendSms([...]) is a separate API (v4) with its own sender identity, credit, and country rules. Wrap it in its own small service, route OTP/alert traffic through it, and log the returned message ID — there's no delivery webhook here, so your own logs are the only trace.
mailjet/mailjet-apiv3-php ^1.6 (needed for the public setAuthentication(), raw-data params, and PHP 8.4 fixes). If your app is on Laravel 10, composer require will resolve to 2.0.x or 1.x — read the resolved version, not the version you typed.Messages[*].Errors / Status. A bad sender address, unverified domain, or malformed address returns a normal response with errors attached. A true-ish return from the facade means "HTTP was fine".php artisan config:cache. After changing mail env vars in a deploy, cached config is the #1 cause of "it still uses the old key". Same for queue:restart — long-lived workers keep the old config and the old package version.Mail::fake() doesn't cover the direct API. The driver path is fakeable; sendEmail()/sendSms() go through Guzzle inside mailjet-apiv3-php, so Http::fake() won't intercept them. Mock your gateway service (or bind a fake implementing the same interface) — don't reach for HTTP fakes and assume coverage.InlinedAttachments, not Attachments.+44...), sender ID configured and in some countries pre-registered (US/Canada A2P), and messages segmented by length — cost scales with segments. Validate and normalise before calling, not after.^2.1, run the app test suite against the driver path and at least one direct sendEmail() integration test against Mailjet's sandbox mode — 2.1's 100% line coverage covers the package, not your payload shapes, which are exactly what changed between v3 and v3.1.How can I help you explore Laravel packages today?