wnx/laravel-sends
Keep track of outgoing emails in your Laravel app and associate sent emails with Eloquent models. Fetch sends per user, product, or mailable class, and store delivery, open, click, bounce, and complaint data.
Installation:
composer require wnx/laravel-sends
Publish the migration and config:
php artisan vendor:publish --provider="Wnx\LaravelSends\LaravelSendsServiceProvider"
php artisan migrate
Enable Tracking:
Add the HasSends trait to your Eloquent models:
use Wnx\LaravelSends\Concerns\HasSends;
class User extends Model
{
use HasSends;
}
First Use Case: Send an email and automatically associate it with models:
Mail::to($user)->send(new ProductReviewMail($product, $user));
The email will now be tracked and linked to both $product and $user.
config/laravel-sends.php (e.g., table name, model associations).database/migrations/[timestamp]_create_laravel_sends_table.php (customize if needed).Send facade and HasSends trait methods.Associating Models with Emails:
HasSends trait on Eloquent models to query sent emails:
$user->sends()->latest()->take(5)->get(); // Get 5 latest emails for the user
send() method to auto-associate:
Mail::to($user)->send(new OrderConfirmationMail($order, $user));
Querying Sent Emails:
$user->sends()->where('subject', 'like', '%review%')->get();Send::forMailClass(OrderConfirmationMail::class)->where('status', 'sent')->get();
Send::where('recipient', '[email protected]')->get();
Customizing Email Tracking:
AppServiceProvider:
use Wnx\LaravelSends\Events\EmailSent;
public function boot()
{
EmailSent::listen(function ($event) {
// Custom logic (e.g., log to external service)
});
}
Testing:
$this->assertCount(1, $user->fresh()->sends());
$this->assertEquals(OrderConfirmationMail::class, $user->sends->first()->mailable);
Queue Integration:
If using queues, ensure the EmailSent event fires by adding this to your AppServiceProvider:
Mail::afterSend(function ($message) {
event(new EmailSent($message));
});
Custom Attributes:
Attach metadata to emails via the Send facade:
Mail::to($user)->send(new ProductReviewMail($product, $user))
->withMetadata(['campaign_id' => 123]);
Soft Deletes:
Enable soft deletes in the laravel-sends table by adding SoftDeletes to the Send model and configuring the deleted_at column in the migration.
Event Listener Conflicts:
EmailSent events are fired multiple times (e.g., due to middleware or other packages), deduplicate logic in the listener or use a queue to avoid duplicates.Model Association Limits:
Send pivot model or use custom queries.Queue Delays:
EmailSent event may fire after the mailable is processed. For immediate tracking, use Mail::send() instead of Mail::queue().Recipient Overrides:
recipient field in the laravel_sends table defaults to the to address. Override this in the EmailSent listener if using custom recipient logic.Missing Records:
EmailSent event is firing by adding a dd() in the listener or checking Laravel logs:
tail -f storage/logs/laravel.log | grep "EmailSent"
laravel-sends table exists and is writable.Query Issues:
Send::query()->toSql() to debug raw queries:
$query = Send::forMailClass(OrderConfirmationMail::class);
dd($query->toSql(), $query->getBindings());
Custom Columns:
Add columns to the laravel_sends table via migration and update the Send model:
Schema::table('laravel_sends', function (Blueprint $table) {
$table->string('custom_field')->nullable();
});
Then access via:
$send->custom_field;
Performance:
mailable, recipient):
Schema::table('laravel_sends', function (Blueprint $table) {
$table->index('mailable');
$table->index('recipient');
});
Bulk Operations: Use chunking for large datasets:
$user->sends()->chunk(100, function ($sends) {
foreach ($sends as $send) {
// Process each send
}
});
Testing:
Send::flush() to clear test data between tests:
public function tearDown(): void
{
Send::flush();
parent::tearDown();
}
Extending the Package:
Send model to add custom methods:
namespace App\Models;
use Wnx\LaravelSends\Models\Send as BaseSend;
class Send extends BaseSend
{
public function isPromotional()
{
return $this->mailable === PromotionalMail::class;
}
}
AppServiceProvider:
Send::swap(new \App\Models\Send());
How can I help you explore Laravel packages today?