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

Swiftmailer Mailgun Bundle Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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:

  1. 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.
  2. Look at these three files in this order:
    • 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.
    • The transport class itself — the only piece worth porting.
  3. If you're on Laravel 9+, skip the bundle entirely and use Laravel's built-in Mailgun mailer:
// 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.

Implementation Patterns

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.

Gotchas and Tips

  • Trust level: low. One star, zero dependents, no ecosystem pressure means no one has found the bugs for you. Read every line of the transport before it touches production, or vendor the file into app/Support/Mailgun/ and delete the dependency.
  • Swiftmailer is abandoned. No security patches, and its 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.
  • Region mismatch is the #1 silent failure. EU domains must use 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.
  • Sandbox domains only deliver to authorized recipients. A 200 response with message: "Queued. Thank you." does not mean delivered — Mailgun silently drops to non-authorized addresses. Verify the domain before you blame the code.
  • API key types differ. Private API key (sending) vs. webhook signing key (verification) vs. legacy sending keys. Don't cross them; the failure is a generic 401.
  • Attachments and inline images break easily. Swiftmailer's 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.
  • Limits you'll hit: ~25 MB per message, 1,000 recipients per API call. Chunk bulk sends: collect($recipients)->chunk(1000) with a queued job per chunk.
  • Rate limits are per-plan (often 300/min). Use ->onQueue('mail') with retryAfter and a RateLimited middleware rather than sleep() in a request cycle.
  • Webhook verification is not optional. HMAC-SHA256 over timestamp . token with the signing key; reject timestamps older than a few minutes to block replays.
  • Debugging: enable Guzzle logging on the transport and log the response body — Mailgun's error 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.
  • Extension points that do work in Laravel: decorate your transport binding, use MessageSending/MessageSent events, and a Mailable base class for shared headers. Symfony's Swift_Events_SendEvent listeners will never fire here.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console