Skip to content

Mailer

The Mailer plugin provides an email sending abstraction with three adapters:

  • NullMailer silently discards all emails, useful for environments where email sending should be disabled.
  • InMemoryMailer stores sent messages in memory for assertion in tests.
  • PhpMailerAdapter sends emails via SMTP using the PHPMailer library.

Why Use It

Direct calls to mail() or PHPMailer create tight coupling to infrastructure. The MailerInterface abstracts email sending so that production code uses SMTP while tests use an in-memory adapter for fast, deterministic assertions without a mail server.

Usage

Messages are built using the fluent MessageBuilder:

$message = MessageBuilder::create()
    ->from('noreply@example.com', 'My App')
    ->to('user@example.com', 'John Doe')
    ->subject('Welcome')
    ->textBody('Welcome to the platform!')
    ->htmlBody('<h1>Welcome</h1><p>Welcome to the platform!</p>')
    ->build()
;

$this->mailer->send($message);

The from(), to(), cc(), and replyTo() methods accept either a string email or an EmailAddress object, with an optional display name.

The bcc() method accepts only an email address (no display name) because BCC headers are stripped during SMTP transmission, making names undeliverable.

Multiple recipients are supported by calling to(), cc(), or bcc() multiple times.

At least one of textBody() or htmlBody() is required. When both are provided, the SMTP adapter sends a multipart message with the HTML as primary body and text as fallback.

Attachments

File attachments and inline content are supported:

$message = MessageBuilder::create()
    ->from('noreply@example.com')
    ->to('user@example.com')
    ->subject('Report')
    ->textBody('Please find the report attached.')
    ->attach('/path/to/report.pdf')
    ->attachContent($csvData, 'data.csv', 'text/csv')
    ->build()
;

Custom Headers

$message = MessageBuilder::create()
    ->from('noreply@example.com')
    ->to('user@example.com')
    ->subject('Notification')
    ->textBody('Body')
    ->header('X-Priority', '1')
    ->header('X-Custom-Tag', 'billing')
    ->build()
;

Testing

The InMemoryMailer adapter allows asserting on sent emails without an SMTP server:

test('Handler sends welcome email', function (): void {
    $mailer = new InMemoryMailer();

    // ... execute handler that sends email ...

    expect($mailer->count())->toBe(1);
    expect($mailer->getLastMessage()->subject)->toBe('Welcome');
    expect($mailer->getLastMessage()->to[0]->getAddress())->toBe('user@example.com');
});

Development

The Docker setup includes a Mailpit service for interactive email testing during development. Mailpit captures all SMTP traffic on port 1025 and provides a web UI on port 8025 to browse sent emails.

The demo application is pre-configured to use Mailpit via app/demo/.env.sample.

See Also