gylaandrij/swiftmailer-mailgun-bundle
Swiftmailer Mailgun Bundle provides a Mailgun transport for Swiftmailer, letting Symfony applications send transactional email through Mailgun's API instead of SMTP. Small, low-traffic package with minimal documentation; useful only if you specifically need this older Swiftmailer integration.
Know what you're getting first: gylaandrij/swiftmailer-mailgun-bundle is a Symfony bundle, not a Laravel package. It ships a DI extension, a Configuration tree, and a Swiftmailer transport service — there is no ServiceProvider, no config/mail.php hook, and nothing auto-registers when you composer require it in a Laravel app. On top of that, Swiftmailer itself reached end-of-life in November 2021. Treat this repository as reference source code for a Mailgun transport, not as a runtime dependency.
Minimal steps:
composer require gylaandrij/swiftmailer-mailgun-bundle in a scratch project (not production) and read what it pulls in — likely mailgun/mailgun-php plus swiftmailer/swiftmailer.DependencyInjection/Configuration.php — the authoritative list of config keys (domain, API key, endpoint/region).Resources/config/services.yml — the transport service definition and its constructor arguments.// composer require symfony/mailgun-mailer
// config/mail.php
'mailgun' => [
'transport' => 'mailgun',
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
],
MAIL_MAILER=mailgun
MAILGUN_DOMAIN=mg.yourdomain.com
MAILGUN_SECRET=key-xxxxxxxx
MAILGUN_ENDPOINT=api.mailgun.net
First use case: sending password resets / order confirmations through your own Mailgun domain with a queued job. Mail::to($user)->queue(new ResetPassword($token)); and you're done — no bundle required.
Pattern 1 — Port the transport into a Laravel mailer driver. If you're on Laravel 8 (Swiftmailer-era) or you specifically want the bundle's transport logic, register it via MailManager::extend:
// app/Providers/MailServiceProvider.php
public function boot(): void
{
Mail::extend('mailgun-bundle', function () {
return new \Your\Ported\MailgunTransport(
config('services.mailgun.secret'),
config('services.mailgun.domain'),
config('services.mailgun.endpoint', 'api.mailgun.net'),
);
});
}
Then 'transport' => 'mailgun-bundle' in config/mail.php. This is the only sane way to keep the bundle in a Laravel app — you own the wiring, the config, and the dependency.
Pattern 2 — Bridge a Symfony service manually. If you must instantiate the bundle's transport directly, build it in a singleton binding with values read from config/services.php. Never rely on %parameter% notation — Laravel has no parameter bag and the bundle's extension will never run.
Pattern 3 — Per-tenant mailers. Mailgun domains map cleanly to Laravel mailers. Define mailgun_brand_a / mailgun_brand_b in config/mail.php, each with its own domain and secret, and select at send time:
Mail::mailer("mailgun_{$tenant->slug}")->to($user)->send(new Invoice($order));
Pattern 4 — Mailgun-specific features via headers. The transport is a thin API wrapper; everything Mailgun-specific rides on headers:
$mailable->withSymfonyMessage(function ($message) use ($order) {
$headers = $message->getHeaders();
$headers->addTextHeader('X-Mailgun-Tag', 'invoice');
$headers->addTextHeader('X-Mailgun-Variables', json_encode(['order_id' => $order->id]));
$headers->addTextHeader('X-Mailgun-Deliver-By', now()->addHours(4)->toRfc2822String());
});
Pattern 5 — Webhooks feed your suppression logic. Mailgun posts delivered, permanent_fail, complained, unsubscribed. Verify the signature, then persist bounces into a suppressed_emails table and check it before every send. Without this you'll keep hammering dead addresses and tank your reputation.
Pattern 6 — Testing. Mail::fake() covers assertions; wrap outbound HTTP with Http::fake(['api.mailgun.net/*' => Http::response(['id' => '<x@mg>', 'message' => 'Queued'], 200)]) when you need to exercise the transport itself. Don't let tests hit Mailgun's sandbox.
app/Support/Mailgun/ and delete the dependency.Swift_Transport interface is incompatible with Symfony Mailer (which Laravel 9+ uses). Any tutorial you find that mentions this bundle is almost certainly Laravel ≤8.api.eu.mailgun.net; sending to api.mailgun.net with an EU key returns 401 with an unhelpful body. Check MAILGUN_ENDPOINT before you debug anything else.message: "Queued. Thank you." does not mean delivered — Mailgun silently drops to non-authorized addresses. Verify the domain before you blame the code.embed() maps to Mailgun's inline multipart field, which must be base64 with an explicit Content-Type. Ported transports that just dump raw bytes produce broken images in Gmail. Test with a real client, not Mailgun's log.collect($recipients)->chunk(1000) with a queued job per chunk.->onQueue('mail') with retryAfter and a RateLimited middleware rather than sleep() in a request cycle.timestamp . token with the signing key; reject timestamps older than a few minutes to block replays.message field names the exact problem ("'to' parameter is not a valid address"). Laravel's MessageSent event gives you the underlying SentMessage but not the API response; wrap the transport if you need both.MessageSending/MessageSent events, and a Mailable base class for shared headers. Symfony's Swift_Events_SendEvent listeners will never fire here.How can I help you explore Laravel packages today?