---
title: "Direct messages — Messages for Laravel | Roundly"
description: "Find-or-create the single 1:1 thread between two participants with one call — always private, never duplicated."
url: https://roundly-consulting.com/open-source/docs/messages-for-laravel/direct-messages
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Messages for Laravel](https://roundly-consulting.com/open-source/docs/messages-for-laravel.md)

# Direct messages

The most common case is a single call — Messages::direct() finds the DM between two models or creates it:

```php
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);          // 0
```

It 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 and looks it up oldest first, so every lookup returns the same thread.

## 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:

```php
$thread = $alice->conversationWith($bob); // find-or-create the DM
$alice->sendMessageTo($thread, 'Hi Bob!');

$bob->unreadCount();          // 1
$bob->markThreadRead($thread);
$bob->unreadCount();          // 0
```

## How 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.
- Closed — once it exists, a direct thread takes no new participant from any caller, trusted ones included, and not even someone who left: participants()->add() throws ParticipationException::directThreadIsClosed(), so nobody can bring a third person into the pair’s history. Adding someone already in the DM still returns their row; to bring someone in, start a group thread.

## Querying direct threads

```php
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)
```

A DM made with the builder — Messages::start()->direct()->withParticipants(\[...\])->create() — gets the same pair key when it has one or two distinct participants (one is a note-to-self), so a second DM for a pair that already has one is refused by the database with a UniqueConstraintViolationException instead of becoming a duplicate. More than two participants throw ParticipationException::directThreadTakesTwo(); a builder DM with no participants stays unkeyed. To find-or-create, use Messages::direct() or conversationWith() — they also take over the key of a DM that no longer qualifies, which the builder does not.

## 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 crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/messages-for-laravel.md)
