henryavila / email-tracking
Track e-mail delivery with Mailgun Hooks. All data are stored in the database on Email model
Installs: 3 193
Dependents: 1
Suggesters: 0
Security: 0
Stars: 7
Watchers: 1
Forks: 0
Open Issues: 3
pkg:composer/henryavila/email-tracking
Requires
- php: ^8.2
- illuminate/contracts: ^11.0|^12.0
- spatie/laravel-package-tools: ^1.9.2
- spatie/laravel-permission: ^6.0
Requires (Dev)
- laravel/pint: ^1.17
- nunomaduro/collision: ^8.1
- nunomaduro/larastan: ^3.2.0
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0.0
- pestphp/pest-plugin-laravel: ^3.0.0
- phpstan/extension-installer: ^1.1
- phpstan/phpstan-deprecation-rules: ^2.0.1
- phpstan/phpstan-phpunit: ^2.0.6
- phpunit/phpunit: ^10.5|^11.0
- dev-main
- v7.0.0
- v6.4.0
- v6.3.1
- v6.3.0
- v6.2.1
- 6.2.0
- v6.1.1
- v6.1.0
- v6.0.0
- v5.3.0
- v5.2.0
- v5.1.1
- v5.1.0
- v5.0.1
- v5.0.0
- v4.0.2
- v4.0.1
- 3.0.0
- 2.2.0
- 2.1.0
- 2.0.0
- 1.2.2
- 1.2.0
- 1.1.0
- 1.0.2.x-dev
- 1.0.2
- v1.0.1.x-dev
- 1.0.1
- 1.0
- dev-dependabot/github_actions/actions/checkout-6
- dev-dependabot/github_actions/actions/cache-5
- dev-email-type-classification
- dev-dependabot/github_actions/stefanzweifel/git-auto-commit-action-7
- dev-fix-delivery-status-error
- dev-html-line
- dev-better-recipient-display
- dev-save-events-on-database
- dev-mail-not-found
- dev-mailgun-typed-events
- dev-dispatch-event-on-webhook-interseption
- dev-blank_line_if
- dev-line_break
- dev-fix-publish-config-file
- dev-fix-middleware
- dev-fix-middlware
- dev-remove-laravel-nova
- dev-henryavila-patch-1
- dev-laravel-11
- dev-log-email-body
- dev-fix+1
- dev-model-connection
This package is auto-updated.
Last update: 2025-12-19 18:27:43 UTC
README
Track email delivery, opens, clicks, and more using Mailgun webhooks. All data is stored in your database for easy querying and analytics.
β¨ Features
- π§ Complete Email Tracking - Track sent, delivered, opened, clicked, bounced, and failed emails
- π Model Association - Link emails to any Eloquent model (User, Order, Invoice, etc.)
- π― Email Categorization - Classify emails by type (transactional, marketing, notifications, etc.)
- π Built-in Analytics - Query delivery rates, open rates, click rates by email type
- πͺ Mailgun Webhooks - Automatic event processing from Mailgun
- π Secure Webhooks - Signature verification for webhook security
- πΎ Database Storage - All email data stored in your database
- π§ͺ Fully Tested - Comprehensive test suite included
- π± Laravel 11+ & 12 - Modern Laravel support with latest features
π Requirements
- PHP 8.2, 8.3, or 8.4 (PHP 8.5 support planned for v7.1.0)
- Laravel 11.0 or higher (Laravel 11 LTS and Laravel 12 supported)
- Mailgun account
π Code Coverage
This package maintains high test coverage with comprehensive unit and integration tests. All new features are fully tested before release.
π¦ Installation
1. Install via Composer
composer require henryavila/email-tracking
2. Publish and Run Migrations
php artisan vendor:publish --tag="email-tracking-migrations"
php artisan migrate
3. Publish Configuration (Optional)
php artisan vendor:publish --tag="email-tracking-config"
4. Configure Mailgun
Setup Laravel Mail with Mailgun driver. See Laravel Mail Documentation.
Add to your .env file:
MAIL_MAILER=mailgun MAILGUN_DOMAIN=yourdomain.com MAILGUN_SECRET=key-99999999999999999999999999999999
5. Setup Mailgun Webhook
In your Mailgun dashboard, add a webhook pointing to:
https://yourdomain.com/webhooks/mailgun
βοΈ Configuration
Register Event Listener
The package needs to listen for sent emails to track them.
Add to AppServiceProvider::boot():
public function boot(): void { \Illuminate\Support\Facades\Event::listen( events: \Illuminate\Mail\Events\MessageSent::class, listener: \HenryAvila\EmailTracking\Listeners\LogEmailSentListener::class ); }
Configuration File
The published config file (config/email-tracking.php) allows customization:
return [ /** * Database connection for Email model (optional) * If null, uses default connection */ 'email-db-connection' => null, /** * Save HTML body of sent emails */ 'log-body-html' => true, /** * Save text body of sent emails */ 'log-body-txt' => true, ];
π Usage
Basic Mailable with Tracking
Extend TrackableMail instead of Laravel's Mailable:
use HenryAvila\EmailTracking\Mail\TrackableMail; class OrderShippedMail extends TrackableMail { public function __construct($order) { $viewData = [ 'order' => $order, 'trackingNumber' => $order->tracking_number, ]; parent::__construct($order, 'emails.order-shipped', $viewData); } }
Send the email:
$order = Order::find(1); Mail::to($order->customer)->send(new OrderShippedMail($order));
The email will be automatically tracked and linked to the $order model.
Trackable Notifications
For notifications, use TrackableNotificationMailMessage:
use HenryAvila\EmailTracking\Notifications\TrackableNotificationMailMessage; class OrderShippedNotification extends Notification { public function __construct(protected Order $order) { } public function toMail($notifiable): MailMessage { return (new TrackableNotificationMailMessage($this->order)) ->subject('Your order has been shipped!') ->line('Your order #' . $this->order->number . ' is on its way.') ->action('Track Shipment', url('/orders/' . $this->order->id)) ->line('Thank you for your purchase!'); } }
Email Type Classification (v7.0.0+)
Categorize emails for better organization and analytics.
1. Create Email Type Enum
<?php namespace App\Enums; enum EmailType: string { case TRANSACTIONAL = 'transactional'; case MARKETING = 'marketing'; case NOTIFICATION = 'notification'; case ADMINISTRATIVE = 'administrative'; case SYSTEM = 'system'; }
2. Implement in Mailable
use App\Enums\EmailType; use HenryAvila\EmailTracking\Mail\TrackableMail; class OrderConfirmationMail extends TrackableMail { protected function getEmailType(): EmailType { return EmailType::TRANSACTIONAL; } }
3. Query by Type
use App\Models\Email; // Get all transactional emails $transactional = Email::where('email_type', 'transactional')->get(); // Add convenient scopes to your Email model Email::transactional()->delivered()->get(); // Analytics by type $stats = Email::select('email_type') ->selectRaw('count(*) as total, sum(opened) as opens') ->groupBy('email_type') ->get();
Learn more: See Email Type Classification Documentation for complete guide with examples.
Querying Emails
use HenryAvila\EmailTracking\Models\Email; // Get all emails for a model $order = Order::find(1); $emails = $order->emails; // Requires ModelWithEmailsSenderTrait on Order model // Query email status $delivered = Email::whereNotNull('delivered_at')->get(); $opened = Email::where('opened', '>', 0)->get(); $clicked = Email::where('clicked', '>', 0)->get(); $failed = Email::whereNotNull('failed_at')->get(); // Get recent emails $recentEmails = Email::orderBy('created_at', 'desc')->limit(10)->get(); // Search by recipient $userEmails = Email::where('to', 'like', '%user@example.com%')->get();
Email Analytics
// Delivery rate $totalSent = Email::count(); $delivered = Email::whereNotNull('delivered_at')->count(); $deliveryRate = ($delivered / $totalSent) * 100; // Open rate $opened = Email::where('opened', '>', 0)->count(); $openRate = ($opened / $delivered) * 100; // Click rate $clicked = Email::where('clicked', '>', 0)->count(); $clickRate = ($clicked / $delivered) * 100;
πͺ Webhook Events
When Mailgun processes an email event (delivered, opened, clicked, etc.), the EmailWebhookProcessed event is dispatched.
Listening to Webhook Events
Create a listener:
<?php namespace App\Listeners; use HenryAvila\EmailTracking\Events\EmailWebhookProcessed; use HenryAvila\EmailTracking\Events\Email\DeliveredEmailEvent; use HenryAvila\EmailTracking\Events\Email\OpenedEmailEvent; class MailgunWebhookProcessedListener { public function handle(EmailWebhookProcessed $event): void { match ($event->emailEvent::class) { DeliveredEmailEvent::class => $this->handleDelivered($event->emailEvent), OpenedEmailEvent::class => $this->handleOpened($event->emailEvent), // Add other events as needed default => null, }; } private function handleDelivered(DeliveredEmailEvent $event): void { // Your custom logic when email is delivered $email = $event->email; logger()->info("Email delivered to {$email->to}"); } private function handleOpened(OpenedEmailEvent $event): void { // Your custom logic when email is opened $email = $event->email; logger()->info("Email opened by {$email->to}"); } }
Register the listener in EventServiceProvider:
protected $listen = [ \HenryAvila\EmailTracking\Events\EmailWebhookProcessed::class => [ \App\Listeners\MailgunWebhookProcessedListener::class, ], ];
Available Event Types
AcceptedEmailEvent- Email accepted for deliveryDeliveredEmailEvent- Email successfully deliveredOpenedEmailEvent- Email opened by recipientClickedEmailEvent- Link clicked in emailPermanentFailureEmailEvent- Permanent delivery failure (bounce)TemporaryFailureEmailEvent- Temporary delivery issueSpamComplaintsEmailEvent- Marked as spamUnsubscribeEmailEvent- Unsubscribe request
π§ Advanced Usage
Model Association
Add the trait to models that send emails:
use HenryAvila\EmailTracking\Traits\ModelWithEmailsSenderTrait; class Order extends Model { use ModelWithEmailsSenderTrait; }
Now you can access emails:
$order = Order::find(1); $emails = $order->emails; // All emails sent for this order
Custom Email Model
Extend the base Email model to add your own methods:
namespace App\Models; use App\Enums\EmailType; use Illuminate\Database\Eloquent\Builder; class Email extends \HenryAvila\EmailTracking\Models\Email { protected function casts(): array { return array_merge(parent::casts(), [ 'email_type' => EmailType::class, ]); } public function scopeTransactional(Builder $query): Builder { return $query->where('email_type', EmailType::TRANSACTIONAL); } public function scopeDelivered(Builder $query): Builder { return $query->whereNotNull('delivered_at'); } public function scopeRecent(Builder $query, int $days = 7): Builder { return $query->where('created_at', '>=', now()->subDays($days)); } }
Use your custom model by binding it in a service provider:
$this->app->bind( \HenryAvila\EmailTracking\Models\Email::class, \App\Models\Email::class );
π Documentation
- Email Type Classification Guide - Complete guide for email categorization
- Changelog - Version history and upgrade guides
π§ͺ 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.
π‘ Upgrade Guides
Upgrading to 7.0.0 from 6.x - BREAKING CHANGES
Version 7.0.0 drops support for PHP 8.1 and Laravel 9-10.
Requirements
- PHP 8.2+ (was 8.1+)
- Laravel 11+ (was 9+)
Why This Change?
Laravel 10 has reached end-of-life and contains known security vulnerabilities. This major version ensures your application uses secure, actively maintained Laravel versions.
Migration Path
# 1. Update your Laravel application to 11.x first composer require laravel/framework:^11.0 # 2. Update PHP to 8.2 or higher (if needed) # 3. Update email-tracking package composer require henryavila/email-tracking:^7.0
Code Changes Required
If you were using Laravel 10's EventServiceProvider pattern, migrate to Laravel 11's AppServiceProvider:
Before (Laravel 10):
// EventServiceProvider protected $listen = [ \Illuminate\Mail\Events\MessageSent::class => [ \HenryAvila\EmailTracking\Listeners\LogEmailSentListener::class, ], ];
After (Laravel 11):
// AppServiceProvider::boot() \Illuminate\Support\Facades\Event::listen( events: \Illuminate\Mail\Events\MessageSent::class, listener: \HenryAvila\EmailTracking\Listeners\LogEmailSentListener::class );
Testing
The package now uses:
- Pest 3.x for testing
- PHPUnit 11.x as test runner
- PHPStan Level 4 for static analysis
All tests pass on PHP 8.2, 8.3, 8.4, and 8.5 with Laravel 11 and 12.
Upgrading to 6.2.0 from earlier versions (Legacy)
Note: If you're on v6.x, upgrade directly to v7.0.0 using the guide above.
A new migration was added to track email events.
php artisan vendor:publish --tag="email-tracking-migrations"
php artisan migrate
π Support
- π Documentation
- π Issue Tracker
- π¬ Discussions
β Show Your Support
Give a βοΈ if this project helped you!