rickdbcn/filament-email
A Filament plugin for managing email in your admin panel: compose and send emails, build reusable templates, and keep track of outgoing mail—all from a clean, native Filament interface.
rickdbcn/filament-email adds an Email section to a Filament panel: a database-backed email log (every message that passes through the plugin's mailer) and email templates you can reuse and resend from the admin UI. The v2.3.0 release is a compatibility-only bump (adds Laravel 13.x support via laravel-shift); there are no new features, config keys, or breaking changes relative to v2.2.0.
Minimal setup:
composer require rickdbcn/filament-email — on Laravel 13 you must be on ^2.3.0; on Laravel 11/12 either ^2.2 or ^2.3 is fine.php artisan vendor:publish --tag="filament-email-migrations"
php artisan migrate
php artisan vendor:publish --tag="filament-email-config"
public function panel(Panel $panel): Panel
{
return $panel
->plugin(\RickDBCN\FilamentEmail\FilamentEmailPlugin::make());
}
Where to look first: the published config/filament-email.php. It controls the model bindings, the storage disk for attachments, and the mail-driver/mailer integration. Reading it before writing any code saves you from fighting defaults later.
First use case: route a single mailer through the plugin and confirm rows land in the log table. Point one mailer in config/mail.php at the plugin's transport, send a Mail::to(...)->send(...) from php artisan tinker, then open the Email resource in the panel. Once you can see that message, you have verified migrations, config, and transport in one shot — everything else is UI and templating on top.
Two independent halves — adopt them separately. The log (capture) and the templates (compose/resend) are decoupled. Many teams only want the audit trail of outbound mail and never touch the template resource; others only want a UI for editing transactional copy. Decide which half you need before wiring the panel, because the template half is the one that pulls in editor assets and policy decisions.
Capture via the mailer, not via Mail::fake()-style interception. The reliable pattern is to give the plugin its own mailer entry in config/mail.php and use that mailer explicitly for the mail you want recorded:
Mail::mailer('filament-email')
->to($user->email)
->send(new InvoiceReady($invoice));
Keeping it as a named mailer rather than swapping the global default means password resets, verification mail, and third-party package mail don't pollute the log — and it makes it trivial to disable capture in a given environment by pointing that mailer back at SMTP.
Templates as the source of truth for editable copy. The pattern that pays off is: define the template row in a seeder (so it exists in every environment, including CI), then have application code resolve it by a stable key and send it with a variable payload. Application code holds the key and the variables; the admin holds the wording. Keep the variable list documented next to the seeder, because there is nothing stopping an editor from renaming {{ customer_name }} in the UI and silently breaking rendering.
Drive sends from Filament actions with a service behind them. Resist putting send logic directly in an Action::make()->action(...) closure. A small MailerService (or a static method on your own wrapper class) that takes a template key + recipient + variables gives you: testability without the panel, one place to add retry/queue logic, and a single seam to swap the plugin out if you ever need to. The Filament action then becomes two lines.
Policies and tenancy are your responsibility. The resources ship with broad defaults. In a multi-tenant panel you will want to scope the email log to the current tenant (otherwise the outbox is a cross-tenant data leak) and gate the template resource so only certain roles can edit copy. Do this with a global scope on the published model plus a policy — both are extension points rather than config flags.
Testing. For behaviour, Mail::fake() still works and is the right tool — assert the mailable, not the DB row. For the log itself, assert directly against the plugin's model after an integration send, and remember the row is written by the transport, so a faked send will not produce one. Test template rendering as a pure unit: given a template body and a variable array, assert the output.
^2.3.0. If you pin ^2.2 you will get an unsatisfiable constraint on Laravel 13. Nothing else changed in this release, so upgrading from 2.2 to 2.3 is a safe, zero-migration bump.composer update covered it.php artisan config:cache and mailer changes. Deploy pipelines that cache config will happily serve a stale mail.php. If mail stops being logged after a deploy, clear config cache before debugging the transport.How can I help you explore Laravel packages today?