symfony/mailjet-mailer
Symfony Mailjet Mailer provides a Mailjet transport bridge for Symfony Mailer, letting applications send transactional and bulk email through Mailjet’s API with standard Symfony Mailer integration.
This is a Symfony Mailer bridge, not a Laravel package. Laravel 9+ ships Symfony Mailer internally, so it slots in — but only if you register a custom transport; there is no built-in mailjet driver in MailManager.
symfony/mailer and symfony/http-client):
composer require symfony/mailjet-mailer
app/Providers/AppServiceProvider.php:
use Illuminate\Support\Facades\Mail;
use Symfony\Component\Mailer\Transport;
public function boot(): void
{
Mail::extend('mailjet', function (array $config) {
return Transport::fromDsn($config['dsn']);
});
}
config/mail.php:
'mailers' => [
'mailjet' => [
'transport' => 'mailjet',
'dsn' => env('MAILJET_DSN'),
],
],
And .env:
MAILJET_DSN=mailjet+api://PUBLIC_KEY:PRIVATE_KEY@default
MAIL_MAILER=mailjet
Mail::to($user)->send(new WelcomeMail($user)) now goes through Mailjet's POST /v3.1/send endpoint.Where to look first in the package: src/Transport/MailjetApiTransport.php (payload shape, header mapping, error handling) and src/Transport/MailjetTransportFactory.php (the exact DSN scheme it accepts). Baseline alternative: if you only need transactional mail and your network allows outbound SMTP, skip this package — Mailjet's SMTP relay works with Laravel out of the box (MAIL_HOST=in-v3.mailjet.com, MAIL_PORT=587, username = API key). The bridge's real value is HTTPS/443-only environments, structured JSON errors, and HttpClient middleware hooks.
Mailables stay untouched. Everything is driven from config. You can override per-send with $mail->mailer('mailjet') on the Mailable, or Mail::mailer('mailjet')->to(...).
Multiple accounts / API keys. Wrap transports with Symfony's RoundRobinTransport inside your Mail::extend callback to spread sends across keys (useful when you run multiple Mailjet sub-accounts or want failover between API and SMTP):
return new \Symfony\Component\Mailer\Transport\RoundRobinTransport([
Transport::fromDsn($apiDsn),
Transport::fromDsn($smtpDsn),
]);
Rate limiting. Free-tier Mailjet is ~4 req/s. Wrap with Symfony\Component\Mailer\Transport\Smtp\…-style limiter or, more simply, push to a Laravel queue and use queue rate limiting. The transport is synchronous; it won't throttle for you.
Correlation and webhooks. Attach Mailjet-recognised headers so events (opens, bounces, spam) can be reconciled against your system:
$mail->getHeaders()->addTextHeader('X-MJ-CustomID', $order->uuid);
$mail->getHeaders()->addTextHeader('X-MJ-EventPayload', json_encode(['tenant' => $tenant->id]));
Register the webhook in Mailjet → Event callbacks, then consume it in a Laravel webhook controller. Custom headers in Symfony's Headers block are passed through to Mailjet's JSON payload — verify the specific X-MJ-* field survives for the version you install.
Logging integration. Hook Laravel's Illuminate\Mail\Events\MessageSending and MessageSent for audit trails. To inspect the raw HTTP request/response, instantiate the transport yourself with a logging HttpClientInterface and return it from Mail::extend — this is the main seam for observability.
Attachments, Reply-To, Bcc, inline images all work through standard Symfony Email APIs — no Mailjet-specific code.
mailjet+api://, not mailjet://. Transport::fromDsn() will throw UnsupportedSchemeException on the wrong scheme. This is the #1 setup failure.@, :, /, etc. Mailjet keys are typically hex-like and safe, but don't assume.Messages[].Status of "error" for rejected recipients. The bridge throws TransportException on per-message failures in current versions — wrap Mail::send() in try/catch when your UI needs to distinguish "queued" from "rejected". MessageSent only fires on success; MessageSending fires regardless.Email objects. To use Mailjet template IDs with variables (TemplateID, Variables in the API body), you must call the API directly or use mailjet/mailjet-apiv3-php. Plan for this if you want marketing-owned templates editable in the Mailjet UI.Mail::fake() for tests. There's no mailjet+sandbox:// DSN.In-Reply-To and References explicitly if you need conversation threading; don't assume the bridge or Mailjet preserves a custom Message-ID.composer why symfony/mailjet-mailer before debugging phantom class-not-found errors.Mail::mailer('mailjet')->raw('test body', function ($m) {
$m->to('[email protected]')->subject('bridge check');
});
Run in tinker with APP_DEBUG=true to see the TransportException message verbatim; it usually names the exact Mailjet error (e.g. sendertype mismatch, unverified sender, quota exceeded).How can I help you explore Laravel packages today?