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

Mandrill Bundle Laravel Package

terehinis/mandrill-bundle

A Laravel bundle that integrates the Mandrill transactional email API, letting you configure API credentials and send template or dynamic emails through a simple, framework-friendly interface.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle. In a Laravel app, "getting started" means getting started with mandrill/mandrill — not with this repository. There is no ServiceProvider, no facade, no extra.laravel autodiscovery, and no bootstrap/app.php equivalent. composer require terehinis/mandrill-bundle will pull a Symfony bundle and its constraints, not a Laravel integration.

Where to look first (in the repo):

  • composer.json — declares type: symfony-bundle, PHP constraint, and Symfony version (~2.x/3.x). This is the fastest way to confirm it won't resolve on Laravel 10–12.
  • <Vendor>/<Bundle>Bundle.php — the Bundle class registered in AppKernel/bundles.php. No Laravel counterpart.
  • DependencyInjection/*Extension.php + Resources/config/services.yml — the config tree (mandrill: api_key: ...) and service wiring. This is the shape you'll mirror in a Laravel service provider.
  • The send/template service — likely a thin wrapper around mandrill/mandrill. Read it for request shape only.

Minimal path a Laravel dev should actually take:

  1. Skip the bundle. composer require mandrill/mandrill (the underlying SDK) or, preferably, call the Mandrill HTTP API with Illuminate\Support\Facades\Http.
  2. Register a transport. In AppServiceProvider::boot():
    Mail::extend('mandrill', fn () => new MandrillTransport(config('services.mandrill.key')));
    
    MandrillTransport implements Symfony\Component\Mailer\Transport\TransportInterface.
  3. Configure. .env: MAIL_MAILER=mandrill, MANDRILL_KEY=.... .env/config/services.php — never a committed YAML.
  4. First use case: transactional only. Password reset → php artisan make:mail PasswordReset → Mail::to($user)->send(new PasswordReset($token)). Prove /users/ping returns ok before wiring anything else.

If you are inheriting a Symfony 2.x/3.x app that already uses this bundle, "getting started" means running the existing sends, reading mandrill: config in config.yml, and using the call sites as a migration checklist to Symfony Mailer's Mailchimp transport.

Implementation Patterns

Pattern 1 — Custom Laravel mail transport (the "correct" integration). Implement Symfony\Component\Mailer\Transport\TransportInterface (Laravel 9+ uses Symfony Mailer under Illuminate\Mail). It receives an Envelope and RawMessage, and you POST to /messages/send. Wire via Mail::extend(). Keep it under 200 lines and inject a PSR-18 client (Guzzle) — never let the transport new a client internally.

Pattern 2 — Bound service for template sends. The Mail facade is rigid for merge_vars, async, subaccount, metadata, and template slugs. Register a small MandrillClient in the container:

$this->app->singleton(MandrillClient::class, fn ($app) =>
    new MandrillClient($app['http'], config('services.mandrill.key')));

Expose sendTemplate(string $slug, string $to, array $vars, array $opts = []) and sendRaw(). Use Laravel's Http client or Guzzle with a hard timeout injected from config (5s).

Pattern 3 — Queue the send. Never send in the request cycle. Wrap the service call in a Job:

SendTransactionalEmail::dispatch($mailable)->onQueue('mail')
    ->tries(3)->backoff([10, 60, 300]);

This decouples user-facing latency from Mandrill's throttle and gives you retries, dead-lettering, and observability for free.

Pattern 4 — Blade vs. Mandrill templates. Pick one, deliberately:

  • Blade-rendered HTML → send as html and version-control templates in git. Preferred for transactional (password resets, receipts).
  • Mandrill dashboard templates + merge_vars → faster for marketing, but the contract is live in the dashboard and drift is silent. Use X-MC-* headers for tagging.

Pattern 5 — Webhook controller. Route POST /webhooks/mandrill, verify X-Mandrill-Signature, then dispatch to handlers keyed on event (send, hard_bounce, soft_bounce, spam, unsub, open, click). Persist raw payloads first (an email_events table), act on them second — you will need to replay.

Pattern 6 — Testing.

  • Mail::fake() covers the facade path but not the transport, so the payload shape is never asserted. Add one feature test that binds Http::fake() and asserts the JSON body.
  • Bind a FakeMandrillClient in AppServiceProvider under APP_ENV=testing.
  • Use Mandrill's test mode + async=false for the one contract test that touches the network; everything else stays offline.

Pattern 7 — Migration from an existing bundle install (Symfony → Laravel, or Mandrill wrapper → Mailer).

  1. grep -R "mandrill" app/ src/ config/ to build the call-site inventory.
  2. Port behind a config flag: MAIL_TRANSPORT=mandrill|mailchimp|ses.
  3. Parallel-run one non-critical stream (e.g. digest notifications) for two weeks; compare delivery rates.
  4. Delete the old call sites only after the parallel run is clean.

Pattern 8 — Subaccount / metadata passthrough. Expose subaccount, metadata, and tags as first-class parameters in the service. Ad-hoc X-MC-* headers scattered across mailables are impossible to audit later.

Gotchas and Tips

  • This is not a Laravel package. No autodiscovery, no package:discover handling, no facade. Any integration is a port. Budget 1–2 dev days for a production-grade transport, not 30 minutes.
  • The SDK is stale. mandrill/mandrill is a thin curl wrapper (~2015 era). It does not respect PSR-18, has limited timeout/retry controls, and may emit PHP 8 deprecations. Prefer Http::withToken(...)->timeout(5)->retry(2, 100)->post('https://mandrillapp.com/api/1.0/...').
  • Mandrill keys ≠ Mailchimp keys. They live in different consoles with different scopes. Mixing them is a common and confusing 401.
  • Mandrill requires a paid Mailchimp plan. Free-tier sends are limited/unavailable on new accounts. Confirm billing before writing code.
  • HTTP 200 with status: "error". Mandrill signals application errors with a 200 response. You must inspect the body, not just the status code. Map at minimum: -1 invalid key, -2 suspended, -3 invalid recipient, -4 payment required, -6 invalid template, -8 invalid domain. Anything unmapped → dead-letter and alert.
  • async=true hides delivery outcomes. It returns a job id you cannot query. For deterministic behaviour in tests and critical paths, use async=false.
  • merge_vars name mismatches fail silently. The email still sends with blanks. Assert the rendered HTML in your Http::fake() feature test.
  • Webhook signature. Header X-Mandrill-Signature is HMAC-SHA1 over the webhook URL followed by sorted POST key/value pairs, keyed with the webhook's signing key — not your API key. Verify before writing anything to the DB. Missing this means spoofable bounce/complaint events.
  • Bounce and complaint handling is out of scope for wrappers of this vintage. You own suppression lists, DKIM/SPF, and unsubscribe flows. Plan for it; don't assume the package covers it.
  • No timeouts = cascading failure. Mandrill slowdowns must not become request-timeouts on POST /password/email. Hard 5s timeout, bounded backoff, circuit breaker with a fallback transport.
  • Config in YAML is a red flag in Symfony 2 era. If you inherit an app with mandrill: api_key: <literal> in config.yml, that key is in git history. Rotate it.
  • Subaccounts should be planned up front for reputation isolation (e.g. transactional vs. marketing). Retrofitting requires re-onboarding sending domains.
  • Extension points you should build yourself regardless of source package: structured error mapping, email_events webhook persistence, suppression list sync, dead-letter queue, per-subaccount rate limiting, X-MC-Tags/X-MC-Metadata passthrough.
  • Licensing. Confirm the LICENSE before forking or copying code out of the repo. MIT is conventional here, but verify — you are copying logic into your own namespace.
  • If you fork, rename the namespace. An internal App\Mail\Mandrill\* is never mistaken for upstream; a Terehinis\... copy is. Budget ~10–15% of one engineer per quarter for API drift, PHP upgrades, and credential rotation.
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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
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
cgv/localized-resource