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

slot/mandrill-bundle

Symfony bundle that integrates Mandrill’s transactional email API, making it easy to configure and send templated messages, manage Mandrill settings, and handle email delivery from PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first: this is a Symfony bundle, not a Laravel package. There is no service provider, no auto-discovery, and no bundle registration path in Laravel. What you actually get is two useful classes — a Message DTO and a Dispatcher that wraps Mandrill's messages/send and messages/send-template endpoints. A Laravel developer should treat it as a small library of value objects, not as an installed bundle. If you want the framework integration (config tree, console commands, webhook controller), you're in the wrong ecosystem — port the pieces you need.

Minimal steps

  1. Inspect before installing. The last release is 2021 and Dependents is 0; its composer.json almost certainly pins Symfony 2/3-era components. Try:

    composer require slot/mandrill-bundle --no-update
    composer why-not slot/mandrill-bundle
    

    In Laravel 10/11 (Symfony 6/7 underneath) expect a conflict. If so, copy Message.php and Dispatcher.php into app/Services/Mandrill/ and adjust the namespace. MIT license, two files, no transitive baggage — this is usually the right call.

  2. Where to look first, in order:

    • README.md — the bundle's Symfony-style config (slot_mandrill: api_key: ...) and controller examples.
    • Slot\MandrillBundle\Message — the full setter surface (setFrom, addTo, setSubject, setHtml, setText, addTag, addAttachment, merge-var helpers). Verify exact names here before building on them.
    • Slot\MandrillBundle\Dispatcher — send() vs sendTemplate() and how the API key reaches it (constructor argument or setApiKey()); mirror what Resources/config/services.xml wires.
    • DependencyInjection/Configuration.php — the only source of truth for supported options.
  3. Wire it once, in Laravel, behind a singleton:

    // config/services.php
    'mandrill' => [
        'key'         => env('MANDRILL_KEY'),
        'webhook_key' => env('MANDRILL_WEBHOOK_KEY'),
    ],
    
    // app/Providers/MandrillServiceProvider.php
    public function register(): void
    {
        $this->app->singleton(\Slot\MandrillBundle\Dispatcher::class, fn () =>
            new \Slot\MandrillBundle\Dispatcher(config('services.mandrill.key'))
        );
    }
    
  4. First use case — a transactional receipt:

    $dispatcher = app(\Slot\MandrillBundle\Dispatcher::class);
    
    $message = $dispatcher->createMessage(); // or `new Message()`
    $message->setFrom('[email protected]', 'Acme Billing');
    $message->addTo($order->user->email, $order->user->name);
    $message->setSubject("Your receipt #{$order->id}");
    $message->setHtml(view('emails.receipt', compact('order'))->render());
    $message->setText("Receipt #{$order->id}");
    $message->addTag('receipt');
    
    $result = $dispatcher->send($message);
    

Implementation Patterns

Wrap it, don't sprinkle it. The bundle has no interfaces. Put it behind your own MandrillMailer contract so a future swap to mailchimp/transactional (the official Mailchimp Transactional SDK, which is what Mandrill is called now) or to Laravel Mail is a one-file change:

final class MandrillMailer implements TransactionalMailer
{
    public function __construct(private Dispatcher $dispatcher) {}

    public function send(OutboundEmail $email): SendResult
    {
        $message = $this->dispatcher->createMessage();
        // ... map your DTO onto the bundle Message
        return SendResult::fromMandrill($this->dispatcher->send($message));
    }
}

Templates over hand-built HTML. sendTemplate($name, $content, $message, $async) is the main reason to keep the package: your copy lives in Mandrill, your code passes merge vars. Mandrill uses *|VAR|* placeholders; $content carries global merge vars plus per-recipient merge_vars keyed by recipient email for batched personalization. Treat template names as config (config/mail_templates.php), not literals in controllers.

Route sends through the queue. Mandrill's own async flag only means "accept now, deliver later" — it does nothing for your app's latency or retry semantics. Push a job:

SendTransactionalEmail::dispatch($email)->onQueue('mail');

tries = 3, backoff = [10, 60], and only retry on connection/timeout errors (see Gotchas — Mandrill has no idempotency key).

Model per-recipient results, not exceptions. The API returns HTTP 200 with an array of per-recipient statuses; treat that array as the source of truth:

foreach ($result as $row) {
    if (in_array($row['status'], ['rejected', 'invalid', 'error'], true)) {
        Log::warning('mandrill.rejected', $row);
        // $row['reject_reason'] => hard-bounce | spam | recipient-rejected | ...
    }
}

Webhooks in Laravel, not the bundle controller. Mandrill POSTs events (send, open, click, hard_bounce, soft_bounce, spam, unsub, inbound). Ignore the bundle's Symfony controller; write a route and verify the signature yourself:

$url    = 'https://app.example.com/webhooks/mandrill';
$params = $request->post();
ksort($params);
$signed = $url;
foreach ($params as $k => $v) { $signed .= $k . $v; }

$expected = base64_encode(hash_hmac('sha1', $signed, config('services.mandrill.webhook_key'), true));
abort_unless(hash_equals($expected, (string) $request->header('X-Mandrill-Signature')), 403);

Then: hard_bounce/spam → mark the address undeliverable in your own table. Never let webhook handling throw before it's persisted — Mandrill retries and you'll get duplicates.

Attachments: pass raw bytes; the bundle/API base64-encodes. Don't inline PDFs from storage into the request body synchronously — Storage::get() + send inside a queued job, and remember base64 inflates payload ~33%.

Gotchas and Tips

  • Dependency rot is the headline risk. 2021 release, 0 dependents, 64 stars. Expect symfony/* constraints that fight Laravel 10/11. Vendoring the two classes is faster and safer than fighting the solver.
  • No bundle boot means no free extras. Any Symfony console commands, container tags, slot_mandrill service id, or config-tree defaults in the README simply won't exist in Laravel. Read the class bodies; ignore the README's config.yml snippets.
  • The API key is read at container build. In Laravel keep env() strictly in config/services.php and run php artisan config:cache after changing it — a stale config cache with the old key is the #1 "it worked in tinker" mystery.
  • Mandrill returns 200 on failure. Don't write try { send() } catch and assume success. Check each result row's status and reject_reason. Log _id so you can find the message in Outbound Activity.
  • Batch sends leak recipients. Multiple addTo() in one message means everyone sees everyone — a real privacy incident waiting to happen. Either one message per recipient, or explicitly setPreserveRecipients(false) / use bcc. Verify the method exists on the bundle's Message first.
  • No idempotency key exists in the API. A retried send() double-sends. Guard with a sent_emails table and a unique constraint on (template, user_id, dedupe_key) before the job runs, not after.
  • Timeouts aren't exposed by the bundle. Set Guzzle-level connect/total timeouts (~5s/15s) in whatever HTTP layer the Dispatcher uses, or the job hangs your queue worker.
  • Dev/test: Mandrill has no sandbox mode. Bind a NullDispatcher (or a logging fake) in local/testing — mirror the bundle's own "disable delivery" config idea, since Mail::fake() doesn't know about this code path.
  • Billing before architecture. Mandrill has been Mailchimp Transactional since 2016; it requires a paid Mailchimp plan. Confirm the account exists and is funded before you build against it — a 402 at launch is a bad day.
  • Extension points are thin: subclass Message to inject default tags/headers/return-path, and decorate your Dispatcher singleton for logging, retry, and fallback. That's the whole surface.
  • If all you need is transactional email in Laravel, skip this entirely: use mailchimp/transactional directly (same endpoints, maintained) or Laravel Mail with a Mandrill-adjacent transport. This bundle earns its place only if you're already on Symfony and want the historic API shape.
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.
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel
vish4395/laravel-translation-manager
morningtrain/economic
teksite/systeminfo-laravel
atomcms/voting
run6/weather
siokas/laravelembeddirectives
adamlien/pkuuids
gilsonsouza/lara-doc-generator
manelgavalda/enrollment_mobile
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics