andrewdyer / mailer
A framework-agnostic PHP library for sending emails from Twig templates, with support for a swappable transport interface
Requires
- php: ^8.3
- twig/twig: ^3.27
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.75
- phpunit/phpunit: ^10.5
- symfony/mailer: ^7.4
Suggests
- symfony/mailer: Required to use SymfonyTransport (^7.4)
README
Mailer
A framework-agnostic PHP library for sending emails from Twig templates, with support for a swappable transport interface.
Introduction
This library provides an email delivery pipeline for PHP applications, rendering Twig templates to HTML and dispatching them through an extensible transport interface. A Symfony Mailer transport is included, with support for custom transports via a simple contract.
Prerequisites
- PHP: Version 8.3 or higher is required.
- Composer: Dependency management tool for PHP.
- Twig: Version ^3.27 is required.
Installation
composer require andrewdyer/mailer
Getting Started
1. Create a Twig template
Create an HTML template in the application's templates directory:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <style> body { font-family: Arial, sans-serif; font-size: 14px; color: #333; padding: 40px; } h1 { font-size: 24px; color: #1a1a2e; } </style> </head> <body> <h1>Welcome, {{ user.name }}</h1> <p>Thanks for signing up.</p> </body> </html>
2. Create a mailable
Extend Mailable and implement envelope() to configure the routing and content() to define the template and data:
use AndrewDyer\Mailer\Mailable; use AndrewDyer\Mailer\Values\Address; use AndrewDyer\Mailer\Values\Content; use AndrewDyer\Mailer\Values\Envelope; class WelcomeMail extends Mailable { public function __construct(private readonly User $user) {} public function envelope(): Envelope { return new Envelope( to: new Address($this->user->email, $this->user->name), subject: 'Welcome to the platform!', ); } public function content(): Content { return new Content( view: 'emails/welcome.html.twig', data: ['user' => $this->user], ); } }
3. Set up the mailer
Instantiate Mailer with a Twig\Environment, a transport, and an optional default from address:
use AndrewDyer\Mailer\Mailer; use AndrewDyer\Mailer\Drivers\SymfonyTransport; use AndrewDyer\Mailer\Values\Address; use Twig\Environment; use Twig\Loader\FilesystemLoader; $twig = new Environment(new FilesystemLoader('/path/to/templates')); $mailer = new Mailer( twig: $twig, transport: new SymfonyTransport('smtp://user:pass@smtp.example.com:587'), defaultFrom: new Address('hello@example.com', 'My App'), );
Usage
Sending a mailable
Dispatch any Mailable instance via the send() method:
$mailer->send(new WelcomeMail($user));
Attaching files
Implement AttachableInterface on the mailable and return an array of absolute file paths:
use AndrewDyer\Mailer\Contracts\AttachableInterface; use AndrewDyer\Mailer\Mailable; class InvoiceMail extends Mailable implements AttachableInterface { public function __construct(private readonly Order $order) {} public function envelope(): Envelope { /* ... */ } public function content(): Content { /* ... */ } public function attachments(): array { return [ '/storage/invoices/invoice-' . $this->order->id . '.pdf', ]; } }
Envelope
A value object defining the routing and metadata for a message. Only to and subject are required — all other properties are optional and can be passed in any order using named arguments:
use AndrewDyer\Mailer\Values\Envelope; use AndrewDyer\Mailer\Values\Address; use AndrewDyer\Mailer\Enums\Priority; new Envelope( to: new Address('recipient@example.com', 'Recipient'), subject: 'Hello!', from: new Address('sender@example.com', 'Sender'), cc: [new Address('cc@example.com')], bcc: [new Address('bcc@example.com')], replyTo: new Address('reply@example.com'), priority: Priority::Normal, );
Setting a from address
The defaultFrom address set on Mailer is used when from is omitted. Set it explicitly on the Envelope to override it for a specific mailable:
public function envelope(): Envelope { return new Envelope( to: new Address($this->user->email, $this->user->name), subject: 'Welcome to the platform!', from: new Address('support@example.com', 'Support Team'), ); }
Adding CC and BCC recipients
Pass arrays of Address instances to cc and bcc on the Envelope:
public function envelope(): Envelope { return new Envelope( to: new Address($this->user->email, $this->user->name), subject: 'Welcome to the platform!', cc: [new Address('manager@example.com', 'Manager')], bcc: [new Address('archive@example.com')], ); }
Setting priority
Pass a Priority enum value to priority on the Envelope:
use AndrewDyer\Mailer\Enums\Priority; public function envelope(): Envelope { return new Envelope( to: new Address($this->user->email), subject: 'Urgent: action required', priority: Priority::High, ); }
Transports
Symfony Mailer
A transport backed by Symfony Mailer, supporting SMTP and a wide range of third-party providers via DSN strings.
composer require symfony/mailer
Then instantiate the transport with a DSN string:
use AndrewDyer\Mailer\Drivers\SymfonyTransport; $transport = new SymfonyTransport('smtp://user:pass@smtp.example.com:587');
Common DSN formats:
smtp://user:pass@smtp.example.com:587
sendmail://default
null://null
Custom transports
Any class implementing TransportInterface can be used as a transport, accepting a PreparedMessage and dispatching it:
use AndrewDyer\Mailer\Contracts\TransportInterface; use AndrewDyer\Mailer\PreparedMessage; class CustomTransport implements TransportInterface { public function send(PreparedMessage $message): void { // Dispatch the message... } }
License
Licensed under the MIT licence and is free for private or commercial projects.