Direct messages
The most common case is a single call — Messages::direct() finds the DM between two models or creates it:
use RoundlyConsulting\Messages\Facades\Messages;
$thread = Messages::direct($alice, $bob); // find-or-create the DM
Messages::send($thread, $alice, 'Hi Bob!');
Messages::unreadCount($bob); // 1
Messages::markRead($thread, $bob);
Messages::unreadCount($bob); // 0It always returns the same direct thread for the same two participants, so you never end up with duplicate DMs. It matches a direct thread whose participants are exactly those two — a third participant would make it a group.
Safe under concurrent first contact
Not even when both sides make first contact at the same moment: the DM carries a unique pair key (direct_key), the database refuses the second insert, and the loser gets the winner’s thread. A DM that was deleted, or that someone left, hands the key to the fresh one.
From the model
The HasMessaging trait offers the same flow as shorthand — conversationWith() calls Messages::direct() under the hood:
$thread = $alice->conversationWith($bob); // find-or-create the DM
$alice->sendMessageTo($thread, 'Hi Bob!');
$bob->unreadCount(); // 1
$bob->markThreadRead($thread);
$bob->unreadCount(); // 0How direct threads differ
- Always private — is_public and everyone_can_join are forced to false, whatever the publicity config says.
- Roleless — participants carry no role, role enforcement is skipped, and setRole() or transferOwnership() throws ParticipationException::directThreadHasNoRoles().
- Unnamed — a direct thread is created without a name.
Querying direct threads
use RoundlyConsulting\Messages\Models\Thread;
Thread::query()->direct()->get(); // every 1:1 thread
Thread::query()->between($alice, $bob)->first(); // the DM of exactly these two, or null
$thread->is_direct; // true
$thread->is_public; // always false for a direct thread
$thread->name; // null
$thread->direct_key; // the unique pair key — Thread::directKeyFor($alice, $bob)Messages::start()->direct()->…->create() creates a new direct thread every time — use Messages::direct() or conversationWith() when you want find-or-create.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.