PHPackages                             consilience/laravel-message-flow - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Queues &amp; Workers](/categories/queues)
4. /
5. consilience/laravel-message-flow

ActiveLibrary[Queues &amp; Workers](/categories/queues)

consilience/laravel-message-flow
================================

Use Laravel queues to push messages between aplications.

3.0.0(4mo ago)5952[1 issues](https://github.com/consilience/laravel-message-flow/issues)MITPHPPHP ^8.4

Since Jun 25Pushed 4mo ago2 watchersCompare

[ Source](https://github.com/consilience/laravel-message-flow)[ Packagist](https://packagist.org/packages/consilience/laravel-message-flow)[ RSS](/packages/consilience-laravel-message-flow/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (5)Dependencies (3)Versions (8)Used By (0)

[![Latest Stable Version](https://camo.githubusercontent.com/500256a7f7b902b87e84fa513dd9f415c2cec0ab9c80f039d67ffa7bd29b2f18/687474703a2f2f706f7365722e707567782e6f72672f636f6e73696c69656e63652f6c61726176656c2d6d6573736167652d666c6f772f76)](https://packagist.org/packages/consilience/laravel-message-flow) [![Total Downloads](https://camo.githubusercontent.com/91ff4b0e325f391473fbaa6fa9201c7d32966076aa5e7e66878d7446469fd62c/687474703a2f2f706f7365722e707567782e6f72672f636f6e73696c69656e63652f6c61726176656c2d6d6573736167652d666c6f772f646f776e6c6f616473)](https://packagist.org/packages/consilience/laravel-message-flow) [![Latest Unstable Version](https://camo.githubusercontent.com/61e6663656737aa8c7cc3613f3377252e34c04827d2ac0a2800c4a5e50733426/687474703a2f2f706f7365722e707567782e6f72672f636f6e73696c69656e63652f6c61726176656c2d6d6573736167652d666c6f772f762f756e737461626c65)](https://packagist.org/packages/consilience/laravel-message-flow) [![License](https://camo.githubusercontent.com/2266f99b8e61e5b33ed66871aa99bdf4b085eebe86b53e4810a4a59a1c17b89c/687474703a2f2f706f7365722e707567782e6f72672f636f6e73696c69656e63652f6c61726176656c2d6d6573736167652d666c6f772f6c6963656e7365)](https://packagist.org/packages/consilience/laravel-message-flow)

Laravel Message Flow
====================

[](#laravel-message-flow)

- [Laravel Message Flow](#laravel-message-flow)
    - [Overview](#overview)
    - [Concepts](#concepts)
        - [Messages](#messages)
        - [Outbound Flow](#outbound-flow)
        - [Inbound Flow](#inbound-flow)
        - [Routing Pipeline](#routing-pipeline)
        - [Message Statuses](#message-statuses)
    - [Configuration Overview](#configuration-overview)
- [Installation](#installation)
    - [Requirements](#requirements)
    - [Install Using Composer](#install-using-composer)
    - [Publish Migrations and Config](#publish-migrations-and-config)
- [Setting Up a Shared Queue (Redis Example)](#setting-up-a-shared-queue-redis-example)
    - [The Three Config Layers](#the-three-config-layers)
    - [Step 1 — Redis Server Entry](#step-1--redis-server-entry)
    - [Step 2 — Queue Entry](#step-2--queue-entry)
    - [Step 3 — Sender: Outbound Routing](#step-3--sender-outbound-routing)
    - [Step 4 — Receiver: Worker and Observer](#step-4--receiver-worker-and-observer)
    - [Two-Way Communication](#two-way-communication)
- [Artisan Commands](#artisan-commands)
    - [Create Message](#create-message)
    - [List Messages](#list-messages)
    - [Purge Messages](#purge-messages)
- [Testing](#testing)

Overview
--------

[](#overview)

Laravel Message Flow is a lightweight messaging system for passing structured data between Laravel applications. It is built entirely on Laravel's queue system — if two applications can connect to the same queue (Redis, database, SQS, or any other driver), they can exchange messages.

There are no external dependencies beyond Laravel itself. No message broker to install, no new protocols to learn — just queue connections you already know how to configure.

The original use-case was to replace fragile webhooks between a suite of applications with something easier to set up, monitor and maintain.

[![Basic Message](docs/basic-message.svg)](docs/basic-message.svg)

Concepts
--------

[](#concepts)

### Messages

[](#messages)

A message is any JSON-serialisable data. Each message carries three things:

- A **UUID** that uniquely identifies the message across applications.
- A **name** used for routing (which queue to send to) and handling (which observer processes it on the receiving side).
- A **payload** — the JSON data itself, portable and self-contained with no dependency on models or classes in the source application.

### Outbound Flow

[](#outbound-flow)

Sending a message is as simple as creating a `MessageFlowOut` model instance. An Eloquent observer detects the new record and dispatches it through a configurable **routing pipeline** that determines which queue connection and queue name to use, then pushes the message onto that queue.

The outbound message is stored in the `message_flow_out` table throughout this process, giving you a local audit trail of what was sent.

### Inbound Flow

[](#inbound-flow)

On the receiving application, a standard Laravel queue worker picks up the job and writes it to the local `message_flow_in` table as a `MessageFlowIn`model. Your application then handles the message via an Eloquent observer — the same pattern you already use for model events.

Once processed, the inbound message can be marked as `complete`, `failed`, or deleted, depending on your application's needs.

### Routing Pipeline

[](#routing-pipeline)

The outbound routing pipeline is a sequence of configurable pipe stages (using Laravel's `Pipeline`) that each message passes through before being dispatched. The default pipeline routes the message to a queue based on its name (via config mappings), dispatches it, and optionally cleans up the local record.

You have full control over this pipeline. You can add custom routing logic, logging, duplicate detection, or anything else — each stage is a simple class implementing a `handle` method.

### Message Statuses

[](#message-statuses)

Both inbound and outbound messages track their lifecycle through a `status`column:

**Outbound (`MessageFlowOut`):**

StatusMeaning`new`Created, waiting to be routed and dispatched`queued`Successfully dispatched to the queue`complete`Fully processed (dispatched, optionally acknowledged)`failed`Could not be dispatched — can be retried**Inbound (`MessageFlowIn`):**

StatusMeaning`new`Received, waiting for the application to handle`complete`Successfully processed by the application`failed`Processing failed — can be retriedFailed messages are never automatically deleted, so they can be inspected and retried. Completed messages can be cleaned up with the `message-flow:purge` artisan command or by enabling the `DeleteCompleteMessage` pipe in the outbound routing pipeline.

Optionally, acknowledgement messages can be sent in the reverse direction to confirm end-to-end delivery:

 ```
sequenceDiagram
    box Application A
        participant AO as Out Model
        participant AI as In Model
    end
    box Shared Queue Driver
        participant QtoB as to-b
        participant QtoA as to-a
    end
    box Application B
        participant BI as In Model
        participant BO as Out Model
        participant BA as Jobs
    end

    Note over AO: Create message
    activate AO
    AO->>QtoB: Message
    activate QtoB
    QtoB->>BI: Message
    deactivate QtoB
    activate BI
    BI->>BO: Create ack message
    activate BO

    BI->>BA: Dispatch to handler
    activate BA
    BA->>BA: Process message
    BA->>BI: Mark complete / delete
    deactivate BA
    deactivate BI

    BO->>QtoA: Ack message
    activate QtoA
    deactivate BO
    QtoA->>AI: Ack message
    deactivate QtoA
    activate AI
    AI->>AO: Delete original message
    deactivate AI
    deactivate AO
```

      Loading Configuration Overview
----------------------

[](#configuration-overview)

RoleWhat to configure**All apps**Shared Redis entry + queue entry (Steps 1-2), identical on every app**Sender**Outbound routing in `message-flow.php` (Step 3)**Receiver**Queue worker + Eloquent observer (Step 4)For two-way communication, each app is both sender and receiver — see [Two-Way Communication](#two-way-communication) below.

Installation
============

[](#installation)

Requirements
------------

[](#requirements)

- PHP `8.4` or higher
- Laravel `10`, `11` or `12`

Install Using Composer
----------------------

[](#install-using-composer)

```
composer require consilience/laravel-message-flow
```

Publish Migrations and Config
-----------------------------

[](#publish-migrations-and-config)

```
php artisan vendor:publish \
    --provider="Consilience\Laravel\MessageFlow\MessageFlowServiceProvider"

```

You can then run `php artisan migrate` to migrate the database.

Setting Up a Shared Queue (Redis Example)
=========================================

[](#setting-up-a-shared-queue-redis-example)

This example uses Redis, but any queue driver supported by Laravel will work. The same four steps apply regardless of driver.

The Three Config Layers
-----------------------

[](#the-three-config-layers)

Laravel uses the word "connection" at several levels, which can be confusing. Setting up a shared queue touches three config files, each at a different layer:

LayerConfig fileWhat it names**Redis server**`config/database.php`How to reach a Redis instance and which key prefix to use**Queue**`config/queue.php`A named queue that uses a particular Redis server entry as its backend**Message Flow**`config/message-flow.php`Which named queue to push messages ontoNo changes to your existing Laravel defaults are needed — we just add new entries alongside them.

Step 1 — Redis Server Entry
---------------------------

[](#step-1--redis-server-entry)

`config/database.php`

Add a Redis entry with a **fixed prefix** so that every application sharing the queue sees the same keys, regardless of each app's global Redis prefix:

```
'redis' => [
    // Your existing options and entries stay unchanged.

    // Shared Redis entry for message flow:
    'message-flow-redis' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_DB', '0'),
        'prefix' => 'message-flow:',
    ],
],
```

Step 2 — Queue Entry
--------------------

[](#step-2--queue-entry)

`config/queue.php`

Add a queue entry that uses the Redis entry from Step 1 as its backend:

```
'connections' => [
    // ...

    'message-flow-queue' => [
        'driver' => 'redis',
        'connection' => 'message-flow-redis', // ← Redis entry from Step 1
        'queue' => 'default',
        'retry_after' => 90,
        'block_for' => null,
    ],
],
```

> **Steps 1 and 2 are identical on every application** sharing the same message flow. They define the shared transport — not who sends or receives.

Step 3 — Sender: Outbound Routing
---------------------------------

[](#step-3--sender-outbound-routing)

`config/message-flow.php`

Tell Message Flow which queue name to push outbound messages onto. **Name each queue after the receiver** — this keeps things unambiguous regardless of how many senders push to it:

```
'out' => [
    'name-mappings' => [
        'default' => [
            'queue-connection' => 'message-flow-queue', // ← queue entry from Step 2
            'queue-name' => 'to-warehouse',             // ← what the receiver listens on
        ],
    ],
],
```

Different message names can route to different receivers:

```
'name-mappings' => [
    'stock-update' => [
        'queue-connection' => 'message-flow-queue',
        'queue-name' => 'to-warehouse',
    ],
    'invoice-ready' => [
        'queue-connection' => 'message-flow-queue',
        'queue-name' => 'to-billing',
    ],
],
```

Send a message by creating a `MessageFlowOut` record:

```
use Consilience\Laravel\MessageFlow\Models\MessageFlowOut;

MessageFlowOut::create([
    'name' => 'stock-update',
    'payload' => ['sku' => 'ABC-123', 'qty' => 50],
]);
```

An Eloquent observer dispatches the message through the routing pipeline automatically — no additional code needed on the sender side.

Step 4 — Receiver: Worker and Observer
--------------------------------------

[](#step-4--receiver-worker-and-observer)

**Start a worker** listening on the queue name that senders push to:

```
php artisan queue:work message-flow-queue --queue=to-warehouse
```

The first argument (`message-flow-queue`) is the queue entry from Step 2. The `--queue` flag is the queue name from the sender's Step 3 config.

**Create an observer** to handle incoming messages:

```
php artisan make:observer MessageFlowObserver \
    --model='Consilience\Laravel\MessageFlow\Models\MessageFlowIn'
```

```
namespace App\Observers;

use Consilience\Laravel\MessageFlow\Models\MessageFlowIn;

class MessageFlowObserver
{
    public function created(MessageFlowIn $message): void
    {
        if (! $message->isNew()) {
            return;
        }

        // Process the message payload.
        // ...

        $message->setComplete()->save();

        // Or on failure:
        // $message->setFailed()->save();
    }
}
```

**Register the observer** in a service provider:

```
use Consilience\Laravel\MessageFlow\Models\MessageFlowIn;
use App\Observers\MessageFlowObserver;

public function boot(): void
{
    MessageFlowIn::observe(MessageFlowObserver::class);
}
```

Two-Way Communication
---------------------

[](#two-way-communication)

When two applications need to send messages **to each other**, both act as sender and receiver. The shared infrastructure (Steps 1–2) stays identical on both apps. Each app just needs:

- Its own **outbound routing** (Step 3) — pointing to the other app's inbound queue
- Its own **queue worker** (Step 4) — listening on its own inbound queue

**Name each queue after the receiver.** This makes it clear who consumes which queue, regardless of how many senders push to it.

Core appFulfilment app**Redis entry** (Step 1)`message-flow-redis``message-flow-redis` (identical)**Queue entry** (Step 2)`message-flow-queue``message-flow-queue` (identical)**Sends to** (Step 3)`queue-name: 'to-fulfilment'``queue-name: 'to-core'`**Worker listens on** (Step 4)`--queue=to-core``--queue=to-fulfilment`Core's `config/message-flow.php`:

```
'name-mappings' => [
    'default' => [
        'queue-connection' => 'message-flow-queue',
        'queue-name' => 'to-fulfilment',
    ],
],
```

Fulfilment's `config/message-flow.php`:

```
'name-mappings' => [
    'default' => [
        'queue-connection' => 'message-flow-queue',
        'queue-name' => 'to-core',
    ],
],
```

Both apps share a **single Redis entry and a single queue entry** — only the outbound `queue-name` and the worker's `--queue` flag differ.

For one-way messaging, only the sender needs Step 3 and only the receiver needs Step 4. For two-way, each app does both.

Artisan Commands
================

[](#artisan-commands)

This package introduces a few new artisan commands:

Create Message
--------------

[](#create-message)

This command allows you to create a new outbound message.

```
php artisan message-flow:create-message \
    --name='routing-name' \
    --payload='{"json":"payload"}' \
    --status=new

```

If no options are provided, the name will be `default`, the status `new` and the payload an empty object.

List Messages
-------------

[](#list-messages)

This command will list the messages currently in the cache tables. These are messages that are being sent, or have been sent and have not yet been deleted. They are also messages that have been received and also not been deleted.

```
php artisan message-flow:list-messages \
    --direction={inbound|outbound} \
    --status={new|complete|failed|other} \
    --uuid={uuid-of-message} \
    --limit=20 \
    --page=1 \
    --process

```

The `status` and `uuid` options can take multiple values.

The `limit` option sets the number of records returned. This is effectively the page size.

The `page` option specifies which page (of size `limit`) to display. Page numbers start at 1 for the first page.

The `process` option will dispatch jobs for messages that have not yet been processed. For outbound messages that will be matching messages in the `new` or `failed` states. This will generally only be needed for testing or kicking off failed observers. For inbound messages in the `new` state, this will fire the eloquent `created` event to kick the custom observers into action.

With the `-v` option, the payload will be included in the listing. Some payloads may be large.

Purge Messages
--------------

[](#purge-messages)

This command deletes old messages from the inbound and/or outbound cache tables. By default it purges records with a `complete` status that were last updated more than 30 days ago.

```
php artisan message-flow:purge \
    --days=30 \
    --hours=0 \
    --direction={inbound|outbound|both} \
    --status={complete|failed} \
    --dry-run

```

The `--days` and `--hours` options are combined to set the age threshold. For example, `--days=1 --hours=12` purges records older than 36 hours, and `--days=0 --hours=6` purges records older than 6 hours.

The `--direction` option defaults to `both`. Abbreviations are accepted (e.g. `--direction=in` or `--direction=out`).

The `--status` option can be specified multiple times to purge records in more than one status. It defaults to `complete` if not specified.

The `--dry-run` option shows how many records would be deleted without actually deleting them.

### Running manually

[](#running-manually)

Purge all completed messages older than 30 days from both tables:

```
php artisan message-flow:purge

```

Purge completed and failed inbound messages older than 7 days:

```
php artisan message-flow:purge --days=7 --direction=inbound --status=complete --status=failed

```

Preview what would be deleted:

```
php artisan message-flow:purge --days=14 --dry-run

```

### Scheduling

[](#scheduling)

To run the purge automatically, add it to your application's scheduler.

In `routes/console.php` (Laravel 11+):

```
use Illuminate\Support\Facades\Schedule;

Schedule::command('message-flow:purge --days=30')->daily();
```

Or in a service provider's `boot()` method:

```
$this->app->booted(function () {
    $schedule = app(\Illuminate\Console\Scheduling\Schedule::class);
    $schedule->command('message-flow:purge --days=30')->daily();
});
```

Testing
=======

[](#testing)

Tests use [Orchestra Testbench](https://github.com/orchestral/testbench) and PHPUnit:

```
composer test
```

TODO
====

[](#todo)

- Names and routing (advanced config).
- Outbound pipeline (advanced config).
- Tests to complete.
- A simple quickstart example with two applications talking to each other.

###  Health Score

48

—

FairBetter than 94% of packages

Maintenance72

Regular maintenance activity

Popularity20

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity74

Established project with proven stability

 Bus Factor1

Top contributor holds 82.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~424 days

Total

5

Last Release

128d ago

Major Versions

1.1.0 → 2.0.02023-06-23

2.0.0 → 3.0.02026-02-15

PHP version history (2 changes)1.0.0PHP ^8.0

3.0.0PHP ^8.4

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/395934?v=4)[Jason Judge](/maintainers/judgej)[@judgej](https://github.com/judgej)

---

Top Contributors

[![judgej](https://avatars.githubusercontent.com/u/395934?v=4)](https://github.com/judgej "judgej (24 commits)")[![bradydan](https://avatars.githubusercontent.com/u/3979917?v=4)](https://github.com/bradydan "bradydan (2 commits)")[![claude](https://avatars.githubusercontent.com/u/81847?v=4)](https://github.com/claude "claude (2 commits)")[![hansensalim](https://avatars.githubusercontent.com/u/22459060?v=4)](https://github.com/hansensalim "hansensalim (1 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/consilience-laravel-message-flow/health.svg)

```
[![Health](https://phpackages.com/badges/consilience-laravel-message-flow/health.svg)](https://phpackages.com/packages/consilience-laravel-message-flow)
```

###  Alternatives

[illuminate/queue

The Illuminate Queue package.

20432.2M1.5k](/packages/illuminate-queue)[illuminate/bus

The Illuminate Bus package.

6045.5M504](/packages/illuminate-bus)[harris21/laravel-fuse

Circuit breaker for Laravel queue jobs. Protect your workers from cascading failures.

43140.3k](/packages/harris21-laravel-fuse)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
