basementdevs / filament-better-mails
This is my package filament-better-mails
Package info
github.com/basementdevs/filament-better-mail
pkg:composer/basementdevs/filament-better-mails
Fund package maintenance!
Requires
- php: ^8.3
- filament/filament: ^5.0
- illuminate/contracts: ^12.0||^13.0
- resend/resend-laravel: ^1.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- driftingly/rector-laravel: ^2.0.4
- larastan/larastan: ^3.0
- laravel/boost: ^2.3
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^11.0.0||^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- pestphp/pest-plugin-livewire: ^4.0
- rector/rector: ^2.0.15
This package is auto-updated.
Last update: 2026-03-22 20:07:16 UTC
README
A Filament v5 plugin that automatically logs every outgoing email, tracks delivery events via provider webhooks, and gives you a full-featured admin panel to browse, preview, and resend emails.
Features
- Zero-config email logging -- every outgoing email is captured automatically
- Webhook-based delivery tracking -- delivered, opened, clicked, bounced, complained, and more
- Email preview -- rendered HTML, raw HTML source, and plain text views
- Resend emails -- individually or in bulk with custom recipients
- Send test emails -- simple or with attachments, directly from the admin panel
- Attachment storage -- stored to disk with download support
- Dashboard stats widget -- delivery, open, click, and bounce rates at a glance
- Fully config-driven -- customize table names, swap models, configure resource navigation
- Auto-pruning -- schedule cleanup of old email records
- Dark mode support
Requirements
- PHP 8.3+
- Laravel 12.x or 13.x
- Filament 5.x
Installation
Install the package via Composer:
composer require basementdevs/filament-better-mails
Publish and run the migrations:
php artisan vendor:publish --tag="filament-better-mails-migrations"
php artisan migrate
Publish the config file:
php artisan vendor:publish --tag="filament-better-mails-config"
Setup
1. Register the Plugin
Add the plugin to your Filament panel provider:
use Basement\BetterMails\Filament\FilamentBetterEmailPlugin; public function panel(Panel $panel): Panel { return $panel // ... ->plugin(FilamentBetterEmailPlugin::make()); }
2. Configure Your Email Provider
Set your provider credentials in .env:
MAIL_MAILER=resend RESEND_API_KEY=your-api-key RESEND_WEBHOOK_SECRET=your-webhook-secret MAILS_WEBHOOK_PROVIDER=resend
3. Register Webhook URL
Point your email provider's webhook settings to:
POST https://your-app.com/webhook/resend
This route is automatically registered and CSRF-exempt.
How It Works
Email Capture Flow
The package hooks into Laravel's mail events automatically. No changes to your existing mail code are required.
YOUR APP BETTER MAILS
| |
| Mail::send(new OrderConfirmation) |
| ----------------------------------------> |
| | BeforeSendingMailListener
| | - Generate tracking UUID
| | - Store email record (subject, body, recipients)
| | - Store attachments to disk
| | - Inject UUID into email headers
| |
| | AfterSendingMailListener
| | - Mark record as sent
| | - Create "Sent" event
| |
Webhook Tracking Flow
When your email provider processes the email, it sends delivery events back:
YOUR APP RESEND RECIPIENT
| | |
| -- sends email --------> | -- delivers ----------> |
| | |
| | <-- opens email ------- |
| | <-- clicks link ------ |
| |
| <-- POST /webhook/resend |
| { event: opened } |
| |
| Updates mail record |
| Creates event timeline |
Tracked Events
| Event | Color | Description |
|---|---|---|
| Sent | Gray | Email dispatched from your app |
| Accepted | Green | Provider accepted the email |
| Scheduled | Amber | Email scheduled for future delivery |
| Delivered | Blue | Email reached recipient's inbox |
| Opened | Green | Recipient opened the email |
| Clicked | Teal | Recipient clicked a link in the email |
| Soft Bounced | Red | Temporary delivery failure |
| Hard Bounced | Red | Permanent delivery failure |
| Complained | Indigo | Recipient marked as spam |
| Unsubscribed | Gray | Recipient unsubscribed |
| Suppressed | Orange | Email suppressed by provider |
Configuration
Full configuration reference
return [ 'mails' => [ 'models' => [ 'mail' => \Basement\BetterMails\Core\Models\BetterEmail::class, 'event' => \Basement\BetterMails\Core\Models\BetterEmailEvent::class, 'attachment' => \Basement\BetterMails\Core\Models\BetterEmailAttachment::class, ], 'database' => [ 'tables' => [ 'mails' => 'mails', 'attachments' => 'mail_attachments', 'events' => 'mail_events', 'polymorph' => 'mailables', ], 'pruning' => [ 'enabled' => false, 'after' => 30, // days ], ], 'headers' => [ 'key' => 'X-Better-Mails-Event-ID', ], 'logging' => [ 'attachments' => [ 'enabled' => env('MAILS_LOGGING_ATTACHMENTS_ENABLED', true), 'disk' => env('FILESYSTEM_DISK', 'local'), 'root' => 'mails/attachments', ], ], ], 'webhooks' => [ 'provider' => env('MAILS_WEBHOOK_PROVIDER', 'resend'), 'logging' => [ 'channel' => env('MAILS_WEBHOOK_LOG_CHANNEL'), 'enabled' => env('MAILS_WEBHOOK_LOGGING_ENABLED', false), ], 'drivers' => [ 'resend' => [ 'driver' => \Basement\BetterMails\Resend\ResendDriver::class, 'key_secret' => env('RESEND_WEBHOOK_SECRET'), ], ], ], 'resource' => [ 'navigation_group' => 'Emails', 'navigation_label' => 'Emails', 'label' => 'Email', 'slug' => 'mails', 'navigation_icon' => 'heroicon-o-envelope', // null to hide icon ], 'view_any' => true, ];
Models
Swap the default models with your own by extending the base classes:
'models' => [ 'mail' => \App\Models\Email::class, 'event' => \App\Models\EmailEvent::class, 'attachment' => \App\Models\EmailAttachment::class, ],
All internal references resolve from config, so your custom models are used throughout the package.
Database Tables
Customize table names to avoid conflicts:
'database' => [ 'tables' => [ 'mails' => 'email_logs', 'attachments' => 'email_attachments', 'events' => 'email_events', 'polymorph' => 'email_mailables', ], ],
Resource Navigation
Customize how the resource appears in the Filament sidebar:
'resource' => [ 'navigation_group' => 'Communications', 'navigation_label' => 'Email Logs', 'label' => 'Email Log', 'slug' => 'email-logs', 'navigation_icon' => 'heroicon-o-inbox', // set to null to hide icon ],
Pruning
Enable automatic cleanup of old records:
'pruning' => [ 'enabled' => true, 'after' => 60, // days ],
Then schedule the prune command in your routes/console.php:
use Illuminate\Support\Facades\Schedule; Schedule::command('model:prune', [ '--model' => \Basement\BetterMails\Core\Models\BetterEmail::class, ])->daily();
Attachment Logging
Control whether attachments are stored to disk:
'logging' => [ 'attachments' => [ 'enabled' => true, // set to false to skip attachment storage 'disk' => 'local', // any filesystem disk 'root' => 'mails/attachments', ], ],
Webhook Logging
Enable detailed webhook logging for debugging:
'webhooks' => [ 'logging' => [ 'enabled' => true, 'channel' => 'webhook', // custom log channel ], ],
Filament Admin Panel
Email List Page
The list page provides:
- Tabs -- filter by event type (All, Sent, Delivered, Opened, Clicked, Bounced, etc.) with badge counts
- Search -- across subject, HTML body, plain text, and recipients
- Sort -- by subject, opens, clicks, or sent date
- Pagination -- 50, 100, or all records
Table Columns
| Column | Description |
|---|---|
| Subject | Email subject line (searchable) |
| Attachments | Paper clip icon if email has attachments |
| Recipient(s) | To addresses |
| Status | Color-coded badges for each event in the timeline |
| Opens | Open count with tooltip showing last opened date |
| Clicks | Click count with tooltip showing last clicked date |
| Sent At | Relative time with exact date tooltip |
Email Detail View
View full email details in a slideOver modal with three sections:
General
- Sender Info tab: from, to, cc, bcc, reply-to, subject
- Statistics tab: open/click counts, timestamps for each delivery stage
- Events tab: chronological timeline of all webhook events
Content
- Preview tab: rendered HTML in an iframe
- HTML tab: raw HTML source with copy button
- Text tab: plain text version with copy button
Attachments
- File metadata (name, size, MIME type)
- Download links
Actions
| Action | Type | Description |
|---|---|---|
| View | Record | Open email detail in a slideOver modal |
| Resend | Record | Resend email to custom recipients (to, cc, bcc) |
| Bulk Resend | Bulk | Resend selected emails, pre-filled with original recipients |
| Send Test Email | Header | Send a simple or attachment test email to verify setup |
| Delete | Bulk | Delete selected email records |
Stats Widget
The dashboard widget shows four metrics as percentages:
| Stat | Color | Description |
|---|---|---|
| Delivered | Green | Delivery rate |
| Opened | Blue | Open rate |
| Clicked | Teal | Click rate |
| Bounced | Red | Bounce rate (soft + hard) |
Each stat links to its corresponding filter tab. Toggle visibility with 'view_any' => false.
Extending the Package
Custom Models
Extend the base models and update the config:
use Basement\BetterMails\Core\Models\BetterEmail; class Email extends BetterEmail { // Add custom relationships, scopes, accessors, etc. }
// config/filament-better-mails.php 'models' => [ 'mail' => \App\Models\Email::class, ],
Custom Webhook Providers
- Create a driver implementing
BetterDriverContract:
use Basement\BetterMails\Core\AbstractMailDriver; class PostmarkDriver extends AbstractMailDriver { public function handle(array $data): void { // Parse webhook payload and dispatch events } }
-
Add the provider enum case to
SupportedMailProvidersEnum -
Register in config:
'webhooks' => [ 'provider' => 'postmark', 'drivers' => [ 'postmark' => [ 'driver' => \App\Mail\Drivers\PostmarkDriver::class, 'key_secret' => env('POSTMARK_WEBHOOK_SECRET'), ], ], ],
Polymorphic Relationships
The package creates a mailables polymorph table for associating emails with any model:
// The migration creates: // mailables (configurable) -> id, mail_id (FK), mailable_type, mailable_id
Publishing Views
Customize the Blade templates:
php artisan vendor:publish --tag="filament-better-mails-views"
| View | Purpose |
|---|---|
preview.blade.php |
Email preview iframe wrapper |
html.blade.php |
HTML content display |
mails/html.blade.php |
HTML source with syntax highlighting |
mails/preview.blade.php |
Iframe preview component |
mails/download.blade.php |
Attachment download button |
mails/test/simple.blade.php |
Simple test email template |
mails/test/attachment.blade.php |
Attachment test email template |
tables/columns/mail-status.blade.php |
Status badge column |
Environment Variables
| Variable | Default | Description |
|---|---|---|
MAIL_MAILER |
smtp |
Laravel mail driver (set to resend) |
RESEND_API_KEY |
-- | Resend API key |
RESEND_WEBHOOK_SECRET |
-- | Resend webhook signing secret |
MAILS_WEBHOOK_PROVIDER |
resend |
Active webhook provider |
MAILS_WEBHOOK_LOGGING_ENABLED |
false |
Enable webhook debug logging |
MAILS_WEBHOOK_LOG_CHANNEL |
-- | Custom log channel for webhooks |
MAILS_LOGGING_ATTACHMENTS_ENABLED |
true |
Store email attachments to disk |
FILESYSTEM_DISK |
local |
Storage disk for attachments |
Supported Providers
| Provider | Status |
|---|---|
| Resend | Supported |
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.