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 Swiftmailer Laravel Package

mailjet/mailjet-swiftmailer

mailjet/mailjet-swiftmailer adds a Mailjet transport to Swift Mailer, letting PHP apps send transactional and bulk email through Mailjet's API. Existing Swift Mailer workflows keep building messages while Mailjet handles delivery, tracking, and scaling.

View on GitHub
Deep Wiki
Context7

Getting Started

This transport only makes sense on Laravel ≤ 8 / SwiftMailer. Confirm before doing anything:

composer show swiftmailer/swiftmailer   # must exist; Laravel 9+ won't have it
  1. Install the package (it pulls mailjet/mailjet-apiv3-php and swiftmailer/swiftmailer):
composer require mailjet/mailjet-swiftmailer
  1. Grab an API Key / Secret from Mailjet → Account → API Key Management (not the SMTP credentials).

  2. Add a mailer to config/mail.php (env values must live here, not in the provider, so config:cache works):

'mailers' => [
    'mailjet' => [
        'transport' => 'mailjet',          // matches the Mail::extend key
        'key'       => env('MAILJET_APIKEY'),
        'secret'    => env('MAILJET_SECRET'),
    ],
],
  1. Register the transport in AppServiceProvider::boot():
use Mailjet\Client;
use Mailjet\MailjetSwiftMailer\SwiftMailer\MailjetTransport;
use Illuminate\Support\Facades\Mail;

Mail::extend('mailjet', function (array $config) {
    $client = new Client(
        $config['key'],
        $config['secret'],
        true,
        ['version' => 'v3.1']   // use v3.1, not the client default v3
    );

    return new MailjetTransport($client);
});
  1. Send normally:
Mail::mailer('mailjet')->to($user->email)->send(new WelcomeMail($user));
// or set MAIL_MAILER=mailjet and use Mail::to(...)->send(...)

Look first at src/SwiftMailer/MailjetTransport.php — it is the whole package (~200 lines). It shows exactly how Swift To/Cc/Bcc, parts, attachments, inline images and X-MJ-* headers are converted into a Messages payload. Then skim Mailjet's "Send API v3.1" docs, since that payload is the real contract you are debugging against.

First useful use case: transactional mail (password reset, receipts) where you want HTTP/API delivery, templating, and correlation IDs instead of SMTP.

Implementation Patterns

Bind the Mailjet client as a singleton so a single HTTP client is reused across sends in a request/worker:

$this->app->singleton(Client::class, fn () => new Client(
    config('mail.mailers.mailjet.key'),
    config('mail.mailers.mailjet.secret'),
    true,
    ['version' => 'v3.1']
));

Mail::extend('mailjet', fn (array $config) => new MailjetTransport(app(Client::class)));

Route only certain mailables through Mailjet without changing the app default:

Mail::mailer('mailjet')->to($user)->send(new InvoiceMail($invoice));

Queue everything. The transport is synchronous; a slow/failing Mailjet call inside a request is a latency incident. Use ShouldQueue mailables or Mail::queue, with $tries/backoff and a failed() hook that logs the Mailjet response.

Inject Mailjet headers via withSwiftMessage when you need Mailjet-hosted templates, variables, or event correlation:

public function build()
{
    return $this->view('emails.welcome')
        ->withSwiftMessage(function (\Swift_Message $message) {
            $h = $message->getHeaders();
            $h->addTextHeader('X-MJ-TemplateID', '123456');
            $h->addTextHeader('X-MJ-TemplateLanguage', '1');
            $h->addTextHeader('X-MJ-Vars', json_encode(['name' => $this->user->name]));
            $h->addTextHeader('X-MJ-CustomID', 'welcome:'.$this->user->id);
            $h->addTextHeader('X-MJ-EventPayload', json_encode(['user' => $this->user->id]));
        });
}

CustomID is the simplest way to correlate Mailjet's Event API/webhooks back to your records.

Always set both parts so Mailjet can build a proper multipart message:

->view('emails.receipt')      // HTML
->text('emails.receipt_text') // TextPart

Attachments / embedded images are handled by the transport (including Swift_EmbeddedFile → InlinedAttachments with the Content-ID you reference as cid: in HTML). Use ->attach($path) / ->embed($path) as usual, and make sure paths exist before queueing — a file deleted between dispatch and execution fails inside the queue worker.

Keep Mail::fake() for feature tests and treat the transport as infrastructure. For transport-level checks, test against Mailjet's sandbox or a real key in a dedicated test account; do not assert on Mailjet internals in unit tests.

Wrap the API call yourself when you need Mailjet-only features the transport doesn't map (bulk Messages[] batching, contacts/lists, campaigns). A small MailjetService using the same injected Client is cleaner than subclassing.

Gotchas and Tips

  • SwiftMailer is EOL and this package last shipped in 2019. It cannot work on Laravel 9+. If you are on 9/10/11, do not try to force it: install symfony/mailjet-mailer and use a DSN (mailjet+api://KEY:SECRET@default) or plain SMTP (in-v3.mailjet.com:587). Migration path first, package second.
  • Laravel 8 + PHP 8.1 may emit deprecations from SwiftMailer even when the package installs cleanly. Pin PHP to 8.0 for older apps or accept the noise; don't --ignore-platform-reqs blindly.
  • Mail::extend must run before the mailer is resolved. Register it in register()/boot() of AppServiceProvider, not lazily in a controller. A missing registration surfaces as "Unsupported mail transport [mailjet]".
  • The closure receives the mailer config array, not the app container. If you use env() inside it, config:cache will return null in production. Env only in config/*.php.
  • send() returns the recipient count, not a message count. A 0 return with no exception usually means the API rejected the message. Inspect $client->getResponse()->getBody() / getStatus() and log it; the Mailjet error body (StatusCode, ErrorMessage) tells you if it's an unvalidated sender, bad template ID, or oversized message.
  • Sender validation is the #1 failure. The From address must be a validated sender or on an authenticated domain in Mailjet, otherwise you get a 400 at API level. Validate once per environment and document it.
  • Size limits: Mailjet caps total message size around 15 MB, and the transport base64-encodes attachments (~33% overhead). Large PDFs attached to a queued mailable are a memory and failure risk — store and link instead.
  • Recipient limits: Mailjet rejects messages with too many recipients (roughly 50 across To/Cc/Bcc). Bulk sends must be Mail::to($oneUserAtATime) or batched via your own API wrapper; the transport does not split for you.
  • No retry/backoff logic in the transport. Rate-limit responses bubble up as exceptions (generic \Exception from the Mailjet client, not a typed transport exception). Catch broadly in the job, release with backoff, and mark failed after N attempts.
  • failover/roundrobin mailers won't rescue you on Laravel 8 with a custom Swift transport; keep Mailjet as a named mailer and implement fallback in your own send path if uptime matters.
  • Bcc, Reply-To and custom headers are converted from the Swift message headers; unknown X-* headers are generally dropped. If a field is absent from the payload, verify against MailjetTransport.php before blaming Mailjet.
  • Debugging tip: enable a short-lived Log::debug() around the response body in a custom transport wrapper, and use Mailjet's message log (search by MessageID / CustomID) to confirm delivery vs. API acceptance. "Accepted by API" ≠ "delivered".
  • Extension point: the cleanest long-term move for a Laravel-heavy codebase is to copy the ~200 lines of MailjetTransport into your app as App\Mail\Transport\MailjetTransport, pin it to the API client directly, and drop the abandoned SwiftMailer dependency as soon as you upgrade past Laravel 8. You keep exact header mapping and gain control over batching and error typing.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core