Weave Code
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

Mailer Laravel Package

draw/mailer

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the Package

    composer require draw/mailer
    

    Ensure draw/core is also installed (dependency).

  2. Configure Default from Address Add to config/packages/draw_post_office.yaml:

    draw_post_office:
        default_from: 'noreply@example.com'
    
  3. Create Your First Email Class Place in src/Email/ForgotPasswordEmail.php:

    namespace App\Email;
    
    use Symfony\Component\Mime\Email;
    
    class ForgotPasswordEmail extends Email
    {
        public function __construct(private string $email) {}
        public function getEmail(): string { return $this->email; }
    }
    
  4. Create a Writer Place in src/Email/ForgotPasswordEmailWriter.php:

    namespace App\Email;
    
    use Draw\Component\Mailer\EmailWriter\EmailWriterInterface;
    
    class ForgotPasswordEmailWriter implements EmailWriterInterface
    {
        public static function getForEmails(): array { return ['compose']; }
    
        public function compose(ForgotPasswordEmail $email)
        {
            $email->to($email->getEmail())
                  ->subject('Reset Password')
                  ->html('<p>Click <a href="#">here</a> to reset.</p>');
        }
    }
    
  5. Send the Email In a controller:

    use App\Email\ForgotPasswordEmail;
    use Symfony\Component\Mailer\MailerInterface;
    
    public function sendForgotPassword(MailerInterface $mailer)
    {
        $mailer->send(new ForgotPasswordEmail('user@example.com'));
    }
    

Where to Look First

  • src/Email/: Store all email classes here.
  • config/packages/draw_post_office.yaml: Configure defaults.
  • EmailWriterInterface: Reference for writer contracts.
  • DefaultFromEmailWriter: Example of a global writer.

Implementation Patterns

1. Email Class Structure

  • Extend Symfony\Component\Mime\Email or TemplatedEmail for Twig templates.
  • Encapsulate Data: Use constructor args for dynamic data (e.g., user email, token).
  • Add Getters: Expose data needed in the writer (e.g., getEmail()).

2. Writer Patterns

  • Single Responsibility: One writer per email class (or group).
  • Dependency Injection: Inject services (e.g., token generators, repositories) into writers.
  • Priority System: Use getForEmails() to register methods with optional priority:
    // Default priority (0)
    public static function getForEmails(): array { return ['compose']; }
    
    // Custom priority (e.g., 100 for higher precedence)
    public static function getForEmails(): array { return ['compose' => 100]; }
    

3. Twig Templates

  • Template Paths: Use htmlTemplate() with relative paths (e.g., 'emails/forgot_password.html.twig').
  • Context Data: Pass dynamic data via context():
    $email->htmlTemplate('emails/welcome.html.twig')
          ->context(['name' => $user->getName()]);
    

4. Global Writers

  • Default from: Configured via draw_post_office.default_from.
  • Global Overrides: Extend DefaultFromEmailWriter to modify all emails:
    class GlobalEmailWriter implements EmailWriterInterface
    {
        public static function getForEmails(): array { return ['*' => 100]; } // Catch-all
    
        public function __invoke(Email $email)
        {
            $email->replyTo('support@example.com');
        }
    }
    

5. Testing

  • Mock Writers: Isolate email logic by mocking writers in tests.
  • Assert Sent Emails: Use Symfony’s Mailer mock:
    $mailer = $this->createMock(MailerInterface::class);
    $mailer->expects($this->once())->method('send');
    

6. Integration with Symfony Mailer

  • Event Hooks: The bundle listens to MessageEvent to trigger writers.
  • Envelope Support: Writers receive an Envelope as the second argument if needed:
    public function compose(ForgotPasswordEmail $email, Envelope $envelope)
    {
        // Customize envelope (e.g., headers, priority)
    }
    

Gotchas and Tips

Pitfalls

  1. Writer Registration Order

    • Writers are called in priority order (highest first). Ensure critical writers (e.g., default from) have higher priority.
    • Fix: Explicitly set priority in getForEmails():
      return ['compose' => 100]; // Higher than default (0)
      
  2. Circular Dependencies

    • Writers may depend on services that aren’t autowired (e.g., custom providers).
    • Fix: Tag services for autowiring or manually bind them in services.yaml:
      services:
          App\Email\ForgotPasswordEmailWriter:
              arguments:
                  $lostPasswordTokenProvider: '@app.lost_password_token_provider'
      
  3. Twig Template Not Found

    • Twig paths are relative to the project root by default. Misconfigured paths cause TemplateNotFoundException.
    • Fix: Use absolute paths or verify twig.paths in config/packages/twig.yaml:
      twig:
          paths: ['%kernel.project_dir%/templates']
      
  4. Email Class Not Matched

    • If the email class isn’t matched to a writer, the email is sent as-is (no writer logic applied).
    • Fix: Ensure getForEmails() returns the correct method name and the email class matches the method’s first argument type.
  5. Symfony Mailer Version Mismatch

    • The bundle is experimental and tied to Symfony Mailer’s state. Upgrades may break compatibility.
    • Fix: Pin symfony/mailer to a stable version in composer.json:
      "symfony/mailer": "6.4.*"
      

Debugging Tips

  1. Log Writer Calls Add debug logs in writers to verify execution:

    public function compose(ForgotPasswordEmail $email)
    {
        \Log::debug('ForgotPasswordEmailWriter called', ['email' => $email->getEmail()]);
        // ...
    }
    
  2. Check Registered Writers Dump the registered writers in a command or controller:

    use Draw\Component\Mailer\EmailWriter\EmailWriterRegistry;
    
    public function debugWriters(EmailWriterRegistry $registry)
    {
        \dd($registry->getWriters());
    }
    
  3. Validate Email Content Use Symfony’s Email dumping for debugging:

    $email = new ForgotPasswordEmail('test@example.com');
    $writer->compose($email);
    \dd($email->getHeaders()->get('To')->getFieldBody());
    

Extension Points

  1. Custom Email Types Extend Email or TemplatedEmail for domain-specific emails (e.g., NewsletterEmail).

  2. Dynamic Writers Implement EmailWriterInterface dynamically (e.g., based on runtime conditions):

    class ConditionalEmailWriter implements EmailWriterInterface
    {
        public static function getForEmails(): array { return ['compose']; }
    
        public function compose(Email $email)
        {
            if ($someCondition) {
                // Custom logic
            }
        }
    }
    
  3. Event Listeners Hook into MessageEvent to modify emails before sending:

    use Symfony\Component\Mailer\Event\MessageEvent;
    
    public function onMessage(MessageEvent $event)
    {
        $email = $event->getMessage();
        $email->getHeaders()->addTextHeader('X-Custom', 'value');
    }
    
  4. Testing Helpers Create a test trait to assert email content:

    trait AssertsEmail
    {
        public function assertEmailSent(MailerInterface $mailer, Email $expectedEmail)
        {
            $sent = $mailer->getSent();
            $this->assertCount(1, $sent);
            $this->assertEquals($expectedEmail->getSubject(), $sent[0]->getSubject());
        }
    }
    
  5. Performance

    • Lazy-Load Writers: For large apps, lazy-load writers to avoid autowiring overhead.
    • Cache Templates: Pre-compile Twig templates for frequent emails.
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.
craftcms/url-validator
directorytree/privacy-filter-classifier
directorytree/privacy-filter
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi
splash/scopes
splash/toolkit
testo/output-teamcity
testo/bridge-symfony