PHPackages                             topoff/laravel-messenger - 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. [Mail &amp; Notifications](/categories/mail)
4. /
5. topoff/laravel-messenger

ActiveLibrary[Mail &amp; Notifications](/categories/mail)

topoff/laravel-messenger
========================

Manages mail templates and mail sending in Laravel

v7.3.2(1mo ago)0141↓50%MITPHPPHP ^8.3

Since Feb 13Pushed 1mo agoCompare

[ Source](https://github.com/topoff/laravel-messenger)[ Packagist](https://packagist.org/packages/topoff/laravel-messenger)[ Docs](https://github.com/topoff/laravel-messenger)[ RSS](/packages/topoff-laravel-messenger/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (31)Versions (94)Used By (0)

laravel-messenger
=================

[](#laravel-messenger)

[![Latest Version on Packagist](https://camo.githubusercontent.com/4952c7dfa4575ae27708783fea7dc4e53df81f42d19b11d7efac15976be6543a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f746f706f66662f6c61726176656c2d6d657373656e6765722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/topoff/laravel-messenger)[![GitHub Tests Action Status](https://camo.githubusercontent.com/f121d46ae4466b46123e9d4866692aec653fb250430d0767670dd3cbd16c4c56/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f746f706f66662f6c61726176656c2d6d657373656e6765722f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/topoff/laravel-messenger/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/ec34da63aeca75a5112f0eb5fe2586137e8d9034ec54904a505d66c5eca49998/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f746f706f66662f6c61726176656c2d6d657373656e6765722f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/topoff/laravel-messenger/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/187bc8b56fe5383d4173af60bc86bcd41b3b5de83b45682d982216759fb9bd65/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f746f706f66662f6c61726176656c2d6d657373656e6765722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/topoff/laravel-messenger)

Template-driven message sending for Laravel with SES/SNS tracking (opens, clicks, delivery, bounce, complaint), automatic retries with exponential backoff, and Nova integration.

Installation
------------

[](#installation)

```
composer require topoff/laravel-messenger
```

Publish the config and migrations:

```
php artisan vendor:publish --tag="messenger-config"
php artisan vendor:publish --tag="messenger-migrations"
php artisan migrate
```

Core Concepts
-------------

[](#core-concepts)

### Models

[](#models)

**Message** — represents a single outgoing message (email or notification):

- Polymorphic relations: `receiver`, `sender`, `messagable` (MorphTo), `messageType` (BelongsTo)
- Status timestamps: `scheduled_at`, `reserved_at`, `error_at`, `sent_at`, `failed_at`
- Tracking fields: `tracking_hash`, `tracking_message_id`, `tracking_opens`, `tracking_clicks`, `tracking_opened_at`, `tracking_clicked_at`, `tracking_content`
- Error fields: `error_code`, `error_message`, `attempts`
- SoftDeletes enabled

**MessageType** — defines how a message is sent:

- `channel` (mail/vonage), `notification_class`, `single_handler`, `bulk_handler`, `direct` flag
- Per-type config: `dev_bcc`, `error_stop_send_minutes`, `max_retry_attempts` (default: 10), `configuration_set`
- Cached via `MessageTypeRepository` (30-day TTL, `messageType` cache tag)

### Contracts

[](#contracts)

Your receiver models must implement `MessageReceiverInterface`:

```
use Topoff\Messenger\Contracts\MessageReceiverInterface;

class User extends Model implements MessageReceiverInterface
{
    public function getEmail(): string { /* ... */ }
    public function getResourceUri(): string { /* ... */ }
    public function setEmailToInvalid(bool $isManualCall = true): void { /* ... */ }
    public function getEmailIsValid(): bool { /* ... */ }
    public function preferredLocale(): string { /* ... */ }
}
```

Mail handlers that support grouping into bulk mails implement `GroupableMailTypeInterface`.

Usage
-----

[](#usage)

### Creating Messages

[](#creating-messages)

Use the fluent `MessageService` builder:

```
use Topoff\Messenger\Services\MessageService;

$service = app(MessageService::class);

$service
    ->setSender(User::class, $user->id)
    ->setReceiver(Company::class, $company->id)
    ->setMessagable(Lead::class, $lead->id)
    ->setMessageTypeClass(NewLeadToCustomerMailHandler::class)
    ->setCompanyId($company->id)
    ->setScheduled(now()->addMinutes(5))
    ->setParams(['key' => 'value'])
    ->setLocale('de')
    ->create();
```

### Scheduling SendMessageJob

[](#scheduling-sendmessagejob)

The package does **not** schedule `SendMessageJob` automatically. You must add it to your application's `routes/console.php`:

```
use Topoff\Messenger\Jobs\SendMessageJob;

// Send new messages every minute
Schedule::job(new SendMessageJob, 'messages')
    ->name(SendMessageJob::class)
    ->withoutOverlapping()
    ->everyMinute();

// Retry failed messages every 10 minutes
Schedule::job(new SendMessageJob(isRetryCallForMessagesWithError: true), 'messages')
    ->everyTenMinutes();
```

### Sending Flow

[](#sending-flow)

1. **MessageService** — fluent builder, persists a `Message` record
2. **SendMessageJob** — picks up pending messages in chunks of 250, routes to single or bulk handler
3. **MainMailHandler** — single message: reserve -&gt; send -&gt; mark sent
4. **MainBulkMailHandler** — groups messages by receiver -&gt; sends `BulkMail`

### Retry Mechanism

[](#retry-mechanism)

Failed messages are retried with exponential backoff (`min(2^(attempts-1) * 15, 960)` minutes):

AttemptBackoff115 min230 min31 hour42 hours54 hours68 hours7+16 hours (capped)Retries stop when `attempts >= max_retry_attempts`, `created_at` exceeds `error_stop_send_minutes`, or the message is marked as permanently failed.

### Permanent Failure Detection

[](#permanent-failure-detection)

These SMTP codes cause immediate permanent failure (`failed_at` is set, no further retries):

CodeMeaning550Mailbox doesn't exist / unroutable553Mailbox name not allowed521Host does not accept mail556Domain does not accept mail—Exception contains "MessageRejected" (SES rejection)Tracking
--------

[](#tracking)

### Open &amp; Click Tracking

[](#open--click-tracking)

When enabled, the `MailTracker` listener hooks into `MessageSending`:

- Injects a 1x1 tracking pixel (``)
- Rewrites links to signed tracking URLs
- Injects `X-SES-CONFIGURATION-SET` and `X-SES-MESSAGE-TAGS` headers
- On `MessageSent`: captures the SES message ID from response headers
- Respects `X-No-Track` header to skip tracking

Config keys:

```
'tracking' => [
    'inject_pixel' => true,
    'track_links' => true,
    'log_content' => true,             // store rendered HTML
    'log_content_strategy' => 'database', // or 'filesystem'
],
```

### Tracking Routes

[](#tracking-routes)

MethodURIPurposeGET`/email/t/{hash}`Open pixel — returns 1x1 GIF, increments opensGET`/email/n?l=...&h=...`Link click — validates signature, increments clicks, redirectsPOST`/email/sns`SNS webhook — processes delivery/bounce/complaint/reject eventsRoute prefix and middleware are configurable via `tracking.route`.

### SNS Event Processing

[](#sns-event-processing)

SNS notifications are dispatched to dedicated jobs:

EventJobEffectDelivery`RecordDeliveryJob`Sets `success: true`, `delivered_at`Bounce`RecordBounceJob`Sets `success: false`, dispatches Permanent/Transient eventComplaint`RecordComplaintJob`Sets `complaint: true`, `success: false`Reject`RecordRejectJob`Sets `success: false`, `failed_at` (permanent)Open`RecordOpenJob`Increments opens, sets `tracking_opened_at`Click`RecordLinkClickJob`Increments clicks, sets `tracking_clicked_at`### BCC Recipient Filtering

[](#bcc-recipient-filtering)

When BCC is added (via `AddBccToEmailsListener`), both TO and BCC recipients share the same SES message ID. The SNS event jobs guard against this by comparing event recipient(s) against `tracking_recipient_contact`. Events for non-matching recipients are skipped. This is case-insensitive and null-safe.

### Events

[](#events)

- `MessageOpenedEvent`, `MessageLinkClickedEvent` — user interaction
- `MessageDeliveredEvent`, `MessagePermanentBouncedEvent`, `MessageTransientBouncedEvent` — delivery status
- `MessageComplaintEvent`, `MessageRejectedEvent` — negative outcomes
- `SesSnsWebhookReceivedEvent` — raw SNS webhook payload

### Listeners

[](#listeners)

ListenerTriggerPurpose`LogEmailsListener``MessageSent`Logs to `email_log` table`LogNotificationListener``NotificationSent`Logs to `notification_log` table`AddBccToEmailsListener``MessageSending`Adds BCC (respects `dev_bcc` per MessageType)SES/SNS Auto Setup
------------------

[](#sessns-auto-setup)

The package can provision all required AWS SES/SNS resources:

- SES Configuration Set + Event Destination (SNS)
- SNS Topic + HTTPS subscription
- SES identities with DKIM + MAIL FROM domains

Enable in config:

```
'ses_sns' => [
    'enabled' => true,
],
```

### Artisan Commands

[](#artisan-commands)

CommandPurpose`messenger:ses-sns:setup-all`Provision all SES identities + SNS tracking in one go`messenger:ses-sns:setup-tracking`Set up SNS topic, subscription, config set, event destination`messenger:ses-sns:check-tracking`Validate tracking infrastructure health`messenger:ses-sns:setup-sending`Set up SES identities with DKIM + MAIL FROM`messenger:ses-sns:check-sending`Validate identity verification and DNS records`messenger:ses-sns:test-events`Simulate SES events (bounce, complaint, delivery)`messenger:ses-sns:teardown`Remove all provisioned resources (requires `--force`)Automatic Cleanup
-----------------

[](#automatic-cleanup)

The package schedules `CleanupMessengerTablesJob` automatically (configurable via `cleanup.schedule`):

```
'cleanup' => [
    'messages_delete_after_months' => 24,
    'email_log_delete_after_months' => 24,
    'notification_log_delete_after_months' => 24,
    'message_tracking_content_null_after_days' => 60,
    'schedule' => [
        'enabled' => true,
        'cron' => '17 3 * * *',
    ],
],
```

Nova Integration
----------------

[](#nova-integration)

When Laravel Nova is installed, the package provides:

**Resources:** Message (full CRUD with tracking fields), MessageType, EmailLog, NotificationLog

**Actions:**

- Resend failed/errored message as new copy
- Preview rendered HTML of sent messages (signed URL)
- Preview message type templates
- Compose ad-hoc custom emails with markdown editor
- Send SMS/email notifications via AnonymousNotifiable
- Open SES/SNS dashboard

**Filters:** Date range, status, message type, receiver type, messagable type

**Lenses:** Tracking stats by message type, by recipient domain, per-message details

**SES/SNS Dashboard** — web UI at `/emessenger/nova/ses-sns-dashboard` with health checks, DNS records, identity details, AWS Console links, and command buttons.

Config:

```
'tracking' => [
    'nova' => [
        'enabled' => true,
        'register_resource' => false, // auto-register in Nova
        'resource' => \Topoff\Messenger\Nova\Resources\Message::class,
    ],
],
```

Configuration Reference
-----------------------

[](#configuration-reference)

SectionKey Settings`models.*`Configurable model classes (message, message\_type, email\_log, notification\_log)`database.*`Connection name`logs.*`Connection, table names for email\_log / notification\_log`cache.*`Tag (`messageType`), TTL (30 days)`cleanup.*`Retention periods, tracking\_content nullification, schedule cron`mail.*`Bulk mail class/view/subject/url, custom message view`sending.*``check_should_send` callable, `prevent_create_message` callable`bcc.*``check_should_add_bcc` callable`tracking.*`Pixel/link injection, route prefix/middleware, Nova config, content storage, SNS topic`ses_sns.*`AWS credentials, configuration sets, SNS topic, event types, tenant, Route53 automationDevelopment
-----------

[](#development)

```
composer test          # Run Pest test suite
composer format        # Laravel Pint
composer analyse       # PHPStan
composer lint          # Pint + PHPStan
composer rector-dry    # Preview Rector refactorings
composer rector        # Apply Rector refactorings
```

The package uses Orchestra Testbench. `php artisan` works in the package root directory.

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Andreas Berger](https://github.com/andreasberger83)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

48

—

FairBetter than 94% of packages

Maintenance92

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity65

Established project with proven stability

 Bus Factor1

Top contributor holds 100% 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 ~0 days

Total

93

Last Release

39d ago

Major Versions

v2.4.4 → v3.0.02026-02-27

v3.2.2 → v4.0.02026-03-03

v4.4.0 → v5.0.02026-03-04

v5.0.0 → v6.0.02026-03-04

v6.12.1 → v7.0.02026-03-27

### Community

Maintainers

![](https://www.gravatar.com/avatar/f1546327b4f6a5440d3a984bf6e3bb771f70d61b244ad2f224f1638c570549b3?d=identicon)[topoff](/maintainers/topoff)

---

Top Contributors

[![ndberg](https://avatars.githubusercontent.com/u/13345669?v=4)](https://github.com/ndberg "ndberg (103 commits)")

---

Tags

laravel-messenger

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/topoff-laravel-messenger/health.svg)

```
[![Health](https://phpackages.com/badges/topoff-laravel-messenger/health.svg)](https://phpackages.com/packages/topoff-laravel-messenger)
```

###  Alternatives

[vormkracht10/laravel-mails

Laravel Mails can collect everything you might want to track about the mails that has been sent by your Laravel app.

24149.7k](/packages/vormkracht10-laravel-mails)[xammie/mailbook

Laravel Mail Explorer

482458.3k1](/packages/xammie-mailbook)[spatie/laravel-notification-log

Log notifications sent by your Laravel app

207902.8k](/packages/spatie-laravel-notification-log)[wnx/laravel-sends

Keep track of outgoing emails in your Laravel application.

200427.3k](/packages/wnx-laravel-sends)[spatie/laravel-discord-alerts

Send a message to Discord

151408.0k](/packages/spatie-laravel-discord-alerts)[backstage/laravel-mails

Laravel Mails can collect everything you might want to track about the mails that has been sent by your Laravel app.

24157.5k5](/packages/backstage-laravel-mails)

PHPackages © 2026

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