Installation
composer require cmgmyr/messenger
php artisan vendor:publish --provider="Cmgmyr\Messenger\MessengerServiceProvider" --tag="migrations"
php artisan migrate
messages and message_recipients tables.Basic Setup
php artisan vendor:publish --provider="Cmgmyr\Messenger\MessengerServiceProvider" --tag="config"
config/messenger.php (e.g., default sender, message TTL).First Use Case: Sending a Message
use Cmgmyr\Messenger\Models\Message;
use Cmgmyr\Messenger\Models\Recipient;
// Create a message
$message = Message::create([
'subject' => 'Welcome!',
'body' => 'Thanks for signing up!',
'sender_id' => auth()->id(), // or a default sender
]);
// Assign recipients (users or models with `recipient_id`)
$message->recipients()->attach([1, 2, 3]); // User IDs
// OR for non-user models:
// $message->recipients()->attach([new Recipient(['recipient_id' => 123, 'recipient_type' => 'Order'])]);
Viewing Messages
hasUnreadMessages() helper or query the messages table directly:
$unread = auth()->user()->unreadMessages()->count();
Sending Messages
$message = Message::create(['subject' => 'Newsletter', 'body' => '...']);
$message->recipients()->sync($userIds); // Replace all recipients
$message = Message::create(['subject' => 'Later', 'body' => '...']);
dispatch(new \Cmgmyr\Messenger\Jobs\SendMessage($message))->delay(now()->addHours(1));
Reading/Marking Messages
auth()->user()->messages()->update(['read_at' => now()]);
$message->delete(); // Uses Laravel's soft deletes
Customizing Recipients
Recipient model for non-user entities (e.g., Order):
class OrderRecipient extends Recipient {
protected $recipientType = 'Order';
}
$message->recipients()->attach(
new OrderRecipient(['recipient_id' => $order->id])
);
Notifications Integration
$message->save();
$message->recipients->each->notify(new \App\Notifications\NewMessage($message));
API Endpoints
routes/api.php):
Route::middleware('auth:sanctum')->group(function () {
Route::get('/messages', [MessageController::class, 'index']);
Route::post('/messages/{message}/read', [MessageController::class, 'markAsRead']);
});
Messenger alongside notifications for multi-channel delivery (e.g., email + in-app).scout driver to Message model for full-text search:
use Laravel\Scout\Searchable;
class Message extends Model {
use Searchable;
}
Message events to trigger external services:
// In EventServiceProvider
protected $listen = [
\Cmgmyr\Messenger\Events\MessageSent::class => [
\App\Listeners\SendSlackAlert::class,
],
];
Recipient Type Mismatches
recipient_type matches the model's class name (e.g., 'User' for App\Models\User).getMorphClass() or explicitly set recipient_type when attaching.Memory Issues with Large Recipient Lists
$recipients->chunk(500)->each(function ($chunk) use ($message) {
$message->recipients()->attach($chunk->pluck('id'));
});
Soft Deletes Confusion
delete() soft-deletes messages but leaves recipients intact.purge() to fully remove:
$message->delete(); // Soft delete
$message->forceDelete(); // Hard delete
Default Sender Overrides
config('messenger.default_sender') is set, it overrides sender_id on creation.['sender_id' => null] to bypass defaults.Event Ordering
MessageSent fires after recipients are attached, so avoid logic that assumes recipients exist in the event handler.$message->recipients->load('user'); // Eager-load relationships
\Cmgmyr\Messenger\Events\MessageSent::dispatch($message);
// Add a listener to log errors
SELECT * FROM message_recipients WHERE recipient_id IS NULL;
Custom Message Models
Message to add fields (e.g., priority, template_id):
class CustomMessage extends Message {
protected $casts = ['priority' => 'integer'];
}
Recipient Validation
validateRecipient() in a custom Recipient model:
public static function validateRecipient($recipientId, $recipientType) {
return Model::where('id', $recipientId)->where('active', 1)->exists();
}
Message Templates
$template = MessageTemplate::find(1);
$message = Message::create([
'subject' => $template->subject,
'body' => view('messages.template', ['data' => $vars])->render(),
]);
Rate Limiting
Route::middleware(['throttle:10,1'])->group(function () {
Route::post('/messages', [MessageController::class, 'store']);
});
Testing
Messenger facade or use partialMock:
$this->partialMock(Message::class, function ($mock) {
$mock->shouldReceive('create')->andReturn($fakeMessage);
});
How can I help you explore Laravel packages today?