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

Post Office Bundle Laravel Package

draw/post-office-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the Bundle

    composer require draw/post-office-bundle
    

    Add to config/bundles.php:

    return [
        // ...
        Draw\Bundle\PostOfficeBundle\PostOfficeBundle::class => ['all' => true],
    ];
    
  2. Configure Default from Address In config/packages/draw_post_office.yaml:

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

    <?php
    namespace App\Email;
    
    use Symfony\Component\Mime\Email;
    
    class ForgotPasswordEmail extends Email
    {
        public function __construct(string $to, string $resetLink)
        {
            $this->to($to)
                 ->subject('Reset Your Password')
                 ->html('<p>Click <a href="' . $resetLink . '">here</a> to reset your password.</p>');
        }
    }
    
  4. Create a Writer Service Place in src/Email/ForgotPasswordEmailWriter.php:

    <?php
    namespace App\Email;
    
    use Draw\Bundle\PostOfficeBundle\Email\EmailWriterInterface;
    
    class ForgotPasswordEmailWriter implements EmailWriterInterface
    {
        public function getForEmails(): array
        {
            return [
                'sendForgotPasswordEmail' => 10, // Priority (higher = called first)
            ];
        }
    
        public function sendForgotPasswordEmail(ForgotPasswordEmail $email)
        {
            // Custom logic (e.g., logging, analytics) before sending
            return $email;
        }
    }
    
  5. Register the Writer as a Service In config/services.yaml:

    services:
        App\Email\ForgotPasswordEmailWriter:
            tags:
                - { name: 'draw.post_office.email_writer' }
    
  6. Trigger the Email in a Controller

    use Symfony\Component\Mailer\MailerInterface;
    
    class ForgotPasswordController extends AbstractController
    {
        public function sendResetLink(User $user, MailerInterface $mailer): Response
        {
            $email = new ForgotPasswordEmail($user->getEmail(), $this->generateResetLink($user));
            $mailer->send($email);
            return $this->redirectToRoute('home');
        }
    }
    

Implementation Patterns

1. Email Class Structure

  • Extend Symfony\Component\Mime\Email for all custom emails.
  • Constructor Injection: Pass required data (e.g., to, resetLink) to the email class.
  • Convention: Store email classes in src/Email/ for clarity.

2. Writer Service Patterns

  • Priority-Based Routing: Higher priority writers are called first.
    public function getForEmails(): array
    {
        return [
            'handlePriorityEmail' => 20, // Higher priority
            'handleDefaultEmail' => 0,  // Default priority
        ];
    }
    
  • Method Matching: The bundle matches methods by the first argument’s class.
    // Only called if the first argument is `ForgotPasswordEmail`
    public function sendForgotPasswordEmail(ForgotPasswordEmail $email) { ... }
    
  • Chaining Writers: Writers can modify the email before it’s sent.
    public function sendForgotPasswordEmail(ForgotPasswordEmail $email)
    {
        $email->text('Plaintext fallback');
        return $email;
    }
    

3. Integration with Symfony Mailer

  • Event Listener: The bundle hooks into Symfony\Component\Mailer\Event\MessageEvent.
  • Default from Address: Override globally in config or per-email:
    $email->from('custom@example.com');
    
  • Async Sending: Use Symfony’s MailerInterface with async transport (e.g., symfony/mailer + doctrine/doctrine-bundle for queues).

4. Testing Emails

  • Mock Writers: Override writers in tests to verify behavior:
    $this->container->set('App\Email\ForgotPasswordEmailWriter', $mockWriter);
    
  • Assert Email Content: Use Symfony\Component\Mime\Email assertions:
    $this->assertEquals('support@example.com', $email->getFrom());
    

5. Dynamic Email Generation

  • Factory Pattern: Create a service to generate emails dynamically:
    class EmailFactory
    {
        public function createForgotPasswordEmail(string $to, string $resetLink): ForgotPasswordEmail
        {
            return new ForgotPasswordEmail($to, $resetLink);
        }
    }
    

Gotchas and Tips

Pitfalls

  1. Priority Collisions

    • If two writers handle the same email class, the highest priority wins.
    • Fix: Ensure unique priorities or method names.
  2. Missing EmailWriterInterface Tag

    • Writers must be tagged as draw.post_office.email_writer or they won’t register.
    • Fix: Verify config/services.yaml or use autoconfiguration.
  3. Symfony Mailer Compatibility

    • The bundle is experimental and may break with major Symfony Mailer updates.
    • Fix: Pin symfony/mailer to a stable version in composer.json.
  4. Circular Dependencies

    • Avoid injecting the MailerInterface into writers (can cause loops).
    • Fix: Pass the Email object directly to writers.
  5. Default from Overrides

    • Per-email from addresses override the global config, but invalid addresses may fail silently.
    • Fix: Validate from addresses in writers:
      if (!$email->getFrom()) {
          $email->from('fallback@example.com');
      }
      

Debugging Tips

  1. Log Writer Calls Add a debug writer to trace execution:

    class DebugEmailWriter implements EmailWriterInterface
    {
        public function getForEmails(): array { return ['debug' => -1000]; }
    
        public function debug(Email $email)
        {
            \Log::debug('Email sent:', [
                'to' => $email->getTo(),
                'subject' => $email->getSubject(),
            ]);
            return $email;
        }
    }
    
  2. Check Registered Writers Dump the registered writers in a controller:

    $writers = $container->get('draw.post_office.email_writer.collection');
    \dump($writers->getWriters());
    
  3. Validate Email Events Use Symfony’s event dispatcher to inspect the MessageEvent:

    $event = new MessageEvent($mailer, $email);
    \dump($event->getMessage());
    

Extension Points

  1. Custom Email Validation Add a writer to validate emails before sending:

    public function validateEmail(Email $email)
    {
        if (empty($email->getTo())) {
            throw new \RuntimeException('Recipient email is missing!');
        }
        return $email;
    }
    
  2. Template-Based Emails Combine with twig/mailer for dynamic templates:

    $email->html($this->renderView('emails/forgot_password.html.twig', ['link' => $resetLink]));
    
  3. Bulk Email Handling Use a writer to batch emails (e.g., for newsletters):

    public function sendBulkEmail(BulkEmail $email)
    {
        foreach ($email->getRecipients() as $recipient) {
            $mailer->send(clone $email->withTo($recipient));
        }
    }
    
  4. Analytics Integration Track email opens/clicks by modifying the email HTML:

    public function addTracking(ForgotPasswordEmail $email)
    {
        $email->html(
            $email->getHtml() .
            '<img src="https://tracker.example.com/pixel?email=' . urlencode($email->getTo()) . '" width="1" height="1" />'
        );
    }
    

Performance Considerations

  • Avoid Heavy Logic in Writers: Keep writers lightweight; offload processing to services.
  • Lazy-Load Writers: Use autowiring and tags to avoid manual service registration.
  • Cache Email Templates: Pre-render HTML templates if emails are static.
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.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky