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

Symfony Mailable Bundle Laravel Package

halloverden/symfony-mailable-bundle

Symfony Mailable Bundle brings Laravel-style mailable classes to Symfony Mailer. Define emails as PHP classes with fluent methods for recipients, subject, and templated content, keeping mail logic organized, testable, and reusable across Symfony apps.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and register

    composer require halloverden/symfony-mailable-bundle
    

    With Flex the bundle should be added to config/bundles.php automatically. If not, add:

    Halloverden\SymfonyMailableBundle\SymfonyMailableBundle::class => ['all' => true],
    

    The bundle wraps Symfony Mailer, so your MAILER_DSN must already be configured (.env), e.g. MAILER_DSN=smtp://localhost:1025 for Mailpit.

  2. Look here first

    • README.md for the supported API surface.
    • vendor/halloverden/symfony-mailable-bundle/src/ — find the base mailable class and the mailer service. With a small, low-traffic package the source is the documentation.
    • Resources/config/services.yaml (or config/services.php) to see the service IDs and whether mailables are autoconfigured/tagged.
  3. First use case — a mailable class plus a send call. The bundle follows a Laravel-style Mailable API: a class with a build() method describing recipients, subject, template, and data.

    // src/Mail/WelcomeMail.php
    namespace App\Mail;
    
    use App\Entity\User;
    use Halloverden\SymfonyMailableBundle\Mailable\AbstractMailable;
    
    final class WelcomeMail extends AbstractMailable
    {
        public function __construct(private User $user) {}
    
        public function build(): self
        {
            return $this
                ->to($this->user->getEmail())
                ->subject('Welcome aboard')
                ->view('emails/welcome.html.twig')
                ->with(['user' => $this->user]);
        }
    }
    
    // src/Controller/RegistrationController.php
    public function register(User $user, MailableMailer $mailer): Response
    {
        // ... persist user
        $mailer->send(new WelcomeMail($user));
    
        return $this->redirectToRoute('dashboard');
    }
    

    Verify the exact base class FQCN and mailer service name in src/ before writing dozens of mailables — the package is young and names may differ from the Laravel analogue.

Implementation Patterns

One mailable per email, constructor = data, build() = presentation. Keep build() free of side effects and database queries. Pass hydrated entities/scalars through the constructor so the mailable can be serialized and queued later. This mirrors the Laravel convention and makes the class trivially testable.

HTML + plain-text alternatives. Use view() for the Twig HTML template and text() (or the bundle's equivalent) for the text part. Symfony Mailer will send a multipart message and clients will pick the best representation.

return $this
    ->to($user->getEmail())
    ->subject('Your invoice')
    ->view('emails/invoice.html.twig')
    ->text('emails/invoice.txt.twig')
    ->with(['invoice' => $invoice]);

Attachments. Prefer path-based attachments for files already on disk; use data attachments only for generated content (PDFs from Dompdf, CSVs).

->attachFromPath($invoice->getPdfPath(), 'invoice.pdf')
->attachData($csv, 'export.csv', 'text/csv')

Async sending via Messenger. If the bundle's mailer dispatches through Symfony Messenger (check for a SendEmailMessage handler or a queueable marker interface), route the message:

# config/packages/messenger.yaml
framework:
    messenger:
        routing:
            'Symfony\Component\Mailer\Messenger\SendEmailMessage': async

If the bundle has no queue support, do it yourself: make the mailable a Messenger message and have a handler call the mailer. This keeps controllers fast and retries transport failures.

Centralise sending in a small service. Rather than injecting the bundle mailer into every controller, wrap it:

final class AppMailer
{
    public function __construct(private MailableMailer $mailer) {}

    public function send(MailableInterface $mailable): void
    {
        $this->mailer->send($mailable);
    }
}

That gives you one place for logging, tenant-specific from addresses, and test doubles.

Testing patterns.

  • Unit: instantiate the mailable, call build(), assert recipient/subject/context without touching a transport.
  • Functional: configure MAILER_DSN=null://null in config/packages/test/mailer.yaml and use Symfony's MailerAssertionsTrait (assertEmailCount, assertEmailSubjectContains) where possible.
  • If the bundle exposes mailable assertions (assertHasSubject, assertHasTo, etc.), prefer them — but verify they exist first.
public function testWelcomeMail(): void
{
    $mail = new WelcomeMail($user);
    $mail->build();

    self::assertSame('Welcome aboard', $mail->getSubject());
    self::assertSame([$user->getEmail()], $mail->getTo());
}

Twig integration tips. Register a dedicated mail template namespace (config/packages/twig.yaml → paths: ['%kernel.project_dir%/templates/emails': emails]) so @emails/welcome.html.twig works in both mailables and previews. For absolute URLs in emails, configure framework.router.default_uri so url() works outside a request context.

Integration points to reuse. Inject mailables into command handlers, domain event listeners, and Messenger handlers — anywhere a notification fires. If the bundle tags mailables, autoconfiguration lets you inject all mailables into a preview command (useful for a local "mail catcher" page).

Gotchas and Tips

  • Maturity risk. Zero stars and zero dependents means no community safety net. Pin the version in composer.json ("halloverden/symfony-mailable-bundle": "0.x.y"), read the source, and be ready to swap it out. If all you need is templated email, Symfony's built-in TemplatedEmail covers 80% of this bundle's value with one fewer dependency.

  • API drift. This is a Laravel-inspired API implemented in Symfony; method names (view, with, attachFromPath, send) may not exactly match Laravel's. Verify against vendor/.../src before relying on a method in production code, and add a thin wrapper (as above) so future renames only break one file.

  • Bundle registration. If Flex doesn't add it, forgetting config/bundles.php produces confusing "service not found" errors rather than an obvious install failure. Check bin/console debug:container | grep -i mail to confirm the service exists.

  • Transport is Symfony Mailer's, not the bundle's. All DSN quirks (smtp://, null://null, TLS options) are inherited. Debug delivery with bin/console messenger:failed:show if async, and start Mailpit/Mailhog locally.

  • Async + serialization. If a mailable carries a Doctrine entity into Messenger, the entity gets serialized and may be stale or detached by the time it sends. Prefer passing IDs/scalars and re-hydrating in the handler, or use Symfony\Component\Messenger\Stamp\TransportNamesStamp deliberately.

  • Attachments and memory. attachFromPath streams; attachData holds the bytes in memory. For large PDFs, write to a temp file and attach from path. Remember to clean up temp files in finally.

  • Rendering vs. sending. Errors in build()/Twig rendering surface as exceptions at send time. Validate templates with a lightweight preview route or command so a broken email is caught in CI, not in a background worker.

  • Localisation. Set the locale per mailable (->locale($user->getLocale())) rather than relying on the request locale — otherwise queued emails render in the worker's default locale.

  • Extension points. Prefer decorating the bundle's mailer service or extending the abstract mailable over editing vendor files. For template overrides, use the Twig namespace approach above. If the bundle dispatches events around sending, hook there for audit logging instead of wrapping every call site.

  • Debugging checklist. (1) Is MAILER_DSN set for the current env? (2) Is the bundle registered? (3) Does the service autowire under the expected interface? (4) Is the template path correct relative to the Twig loader? (5) For async, is the transport worker actually running?

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.
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
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core