jeffersongoncalves/laravel-mail
Complete email management for Laravel: logs outgoing mail, database Blade templates with translations and versioning, webhook delivery tracking (SES/SendGrid/Postmark/Mailgun/Resend), open/click pixel tracking, suppression list, CSS inlining, List-Unsubscribe, preview, stats, retries.
composer require jeffersongoncalves/laravel-mail
php artisan vendor:publish --tag="laravel-mail-migrations"
php artisan migrate
Mail::to('user@example.com')->send(new WelcomeMail($user));
mail_logs table.php artisan vendor:publish --tag="laravel-mail-config"
.env:
LARAVEL_MAIL_TRACKING_ENABLED=true
LARAVEL_MAIL_PIXEL_OPEN_TRACKING=true
// Create template via Tinker or migration
MailTemplate::create([
'key' => 'welcome',
'subject' => ['en' => 'Welcome!'],
'html_body' => ['en' => '<h1>Hello!</h1>'],
]);
Workflow:
mail_templates table (supports spatie/laravel-translatable).TemplateMailable:
class WelcomeEmail extends TemplateMailable {
public function templateKey(): string { return 'welcome'; }
public function templateData(): array { return ['name' => $this->user->name]; }
}
Mail::to()->send(new WelcomeEmail($user)).Integration Tip:
PreviewTemplateAction for admin previews:
$preview = app(PreviewTemplateAction::class)->execute($template, ['name' => 'Alice']);
Workflow:
config/laravel-mail.php (e.g., sendgrid.signing_secret).Event::listen(MailBounced::class, function ($event) {
$event->mailLog->suppress(); // Auto-suppress bounces
});
https://app.com/webhooks/mail/sendgrid).Integration Tip:
MailLog::find($id)->trackingEvents to query historical events.Workflow:
'pixel' => [
'open_tracking' => true,
'click_tracking' => true,
],
RouteServiceProvider):
Route::mailTracking(); // Mounts /mail/t/pixel and /mail/t/click
opened/clicked events.Integration Tip:
'pixel' => [
'route_prefix' => 'track',
'route_middleware' => ['throttle:60'],
],
Workflow:
failed (status: failed).php artisan mail:retry --attempts=3
$log = MailLog::where('status', 'failed')->first();
$log->retry();
Integration Tip:
MailLog::failed()->withRetryAttemptsLessThan(3) to query retryable emails.Workflow:
TemplateMailable and use with Notification:
$user->notify(new WelcomeNotification($user));
config/notifications.php:
'channels' => [
'mail' => [
'transport' => 'laravel-mail',
],
],
Integration Tip:
via() in your notification:
public function via($notifiable) {
return ['laravel-mail'];
}
Webhook Validation Failures:
LARAVEL_MAIL_[PROVIDER]_SIGNING_SECRET in .env (e.g., LARAVEL_MAIL_SENDGRID_SIGNING_SECRET).mail_webhook_attempts table for failed attempts.Pixel Tracking Blocked:
LARAVEL_MAIL_TRACKING_ENABLED=true) as a fallback.Template Versioning Overhead:
mail_template_versions.$template->update(['versioned' => false]);
CSS Inlining Performance:
LARAVEL_MAIL_INLINE_CSS=false
Suppression List Conflicts:
MailSuppression::with('mailLog')->get();
Webhook Debugging:
LARAVEL_MAIL_TRACKING_LOG_WEBHOOKS=true
mail_webhook_attempts table for payloads.Pixel Tracking:
src="/mail/t/pixel/).curl:
curl -v "https://app.com/mail/t/pixel/123?sig=abc"
Template Rendering:
dd(app(PreviewTemplateAction::class)->execute($template, $data));
mail_template_versions.Attachment Storage:
LARAVEL_MAIL_ATTACHMENT_DISK is configured if storing attachments:
LARAVEL_MAIL_ATTACHMENT_DISK=s3
Custom Tracking Events:
MailTrackingEvent model or create custom events by listening to MailLogUpdated:
Event::listen(MailLogUpdated::class, function ($event) {
if ($event->mailLog->status === 'custom_status') {
// Dispatch custom event
}
});
Override Models:
php artisan vendor:publish --tag="laravel-mail-models"
MailLog fields:
class CustomMailLog extends MailLog {
protected $casts = [
'custom_field' => 'string',
];
}
Custom Webhook Handlers:
WebhookHandler service provider:
class CustomWebhookServiceProvider extends WebhookServiceProvider {
protected function registerHandlers() {
$this->app->bind(WebhookHandler::class, CustomWebhookHandler::class);
}
}
Multi-Tenant Scoping:
'multi_tenant' => [
'enabled' => true,
'tenant_model' => \App\Models\Tenant::class,
'tenant_key' => 'id',
],
MailLog::forTenant($tenant) for scoped queries.Attachment Storage:
LARAVEL_MAIL_ATTACHMENT_DISK is set, attachments are stored in mail_attachments table with files on the specified disk.Pixel Signing Key:
APP_KEY if LARAVEL_MAIL_PIXEL_SIGNING_KEY is empty.APP_KEY.Template Layouts:
config/laravel-mail.php:
'templates' => [
'default_layout' => '<html><body>{!! $slot !!}</body></html>',
],
$template->update(['layout' => '<div>{!! $slot !!}</div>']);
Unsubscribe Headers:
{email}) are replaced atHow can I help you explore Laravel packages today?