vanio/vanio-mailing-bundle
Vanio Mailing Bundle is a Symfony bundle that streamlines transactional email: build reusable mailers, render templated messages, and send through your configured transport with a clean, service-oriented API.
First, the important framing: vanio/vanio-mailing-bundle is a Symfony bundle, not a Laravel package. The -bundle suffix, the Vanio vendor (a Symfony shop), and the lack of a Laravel service provider all point the same way. So "getting started" for a Laravel developer is really a decision tree:
composer.json and look at require and extra. If you see symfony/framework-bundle, symfony/dependency-injection, and a DependencyInjection/ + Resources/config/services.* structure, this is Symfony-only. There is no VanioMailingServiceProvider and nothing to php artisan vendor:publish.config/bundles.php, then inspect DependencyInjection/Configuration.php for the config tree. That file tells you the bundle's real surface area — sender address, default transport, template paths, queue toggle. That's the first file to read.Mail + Mailable + Blade. Porting the idea takes an afternoon; depending on the package drags in Symfony DI.DependencyInjection/ (what's configurable), then the top-level service class the bundle exposes (usually a Mailer or MailingManager). The public methods on that class are the actual API; everything else is wiring.First use case: transactional mail with a consistent sender and a reusable template. If the bundle exposes a send()-style method that takes a recipient, a subject, and a template name, that's the whole value proposition — you stop hand-assembling Envelope/Email/Address objects at every call site and centralize sender + template resolution in config.
Constructor-inject the exposed mailer service, don't reach for the container. Whatever the bundle's public entry point is (likely a Mailer service autowired by type), depend on that interface/class directly. In a Symfony app that's standard autowiring; in a Laravel-port you'd bind an equivalent MailingService in a service provider and inject that. Avoid app()/$container->get() lookups — they hide the dependency and make testing painful.
Treat configuration as the sender registry. These bundles typically let you define named senders/transports in config (vanio_mailing: senders: { default: {...}, no_reply: {...} }). The pattern to adopt: one named sender per audience or purpose (support@, no-reply@, billing@), select it by name at the call site, and never pass an inline From address. This is the payoff — sender addresses live in one reviewed config file instead of being sprayed across the codebase.
Wrap it, don't sprinkle it. Create a thin application-level service (e.g. App\Mail\TransactionalMailer) that depends on the bundle's mailer and exposes intent-named methods: sendPasswordReset(), sendInvoice(), sendWelcome(). This gives you:
Mockery::mock(TransactionalMailer::class)).Mail or Symfony Mailer directly, call sites don't change.Templates: expect Twig, plan for Blade. A Symfony bundle will render Twig templates (look for Twig\Environment in the service constructor). If you're porting to Laravel, keep the layout inheritance idea — a base email layout with a content block — and rebuild it in Blade (@extends('emails.layout'), @yield('content')). Don't try to run Twig inside Laravel for this; you'll lose Blade components and it's not worth the dependency.
Test with a null/log transport, not real sends. Symfony Mailer supports a null:// or array transport; configure the bundle's transport to the in-memory/log variant in config/packages/test/ and assert on captured messages. In a Laravel port, Mail::fake() is the direct equivalent. The pattern is the same: assert on the composed message (recipient, subject, rendered body), never on the transport being called for real.
Integration tip — queue at the boundary, not inside the bundle. Unless the bundle explicitly documents async support, assume send() is synchronous and blocks on the transport. Queue the calling job (ShouldQueue / Laravel queue) rather than expecting the bundle to do it. This keeps the bundle stateless and your retry/backoff policy in your own code.
Configuration.php. These bundles rarely document every config key. The Configuration class (or services.yaml in Resources/config) is the source of truth for what's actually settable, and it's where you'll find the transport/sender knobs.From address and default transport are set. A missing default sender produces confusing "empty From" exceptions at runtime rather than at boot; set it explicitly in config, not in code.%kernel.project_dir%-relative path or a Twig namespace, a missing template throws deep in the render step with a stack trace that doesn't name your call site. When debugging "mail not sending," check template existence/namespace before checking the transport.Resources/config/services.* are usually overridable — you can redefine the mailer service ID in your own services.yaml to swap in a decorator (e.g. one that adds headers or logs). That's the clean way to customize without forking.Twig\Environment and SwiftMailer/Symfony\Component\Mailer coupling in the service constructor. If the bundle still references SwiftMailer (EOL since 2021), that's a red flag — treat it as an abandoned dependency and port the pattern rather than adopt it.composer why-not vanio/vanio-mailing-bundle '*' in a scratch Symfony project and read the transitive deps. If it drags in an old Symfony major or SwiftMailer, walk away and reimplement — the concept is ~100 lines of code.How can I help you explore Laravel packages today?