PHPackages                             jiannius/mailog - 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. [Logging &amp; Monitoring](/categories/logging)
4. /
5. jiannius/mailog

ActiveLibrary[Logging &amp; Monitoring](/categories/logging)

jiannius/mailog
===============

Jiannius Mailog - outgoing email audit logging for Laravel

00PHPCI passing

Since Jun 9Pushed yesterdayCompare

[ Source](https://github.com/jiannius/mailog)[ Packagist](https://packagist.org/packages/jiannius/mailog)[ RSS](/packages/jiannius-mailog/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

Jiannius Mailog
===============

[](#jiannius-mailog)

Audit-log every outgoing email your Laravel app sends — straight to the database, with no code changes. Mailog listens to Laravel's mail events and records each message (sender, recipients, subject, bodies, attachments, tags, the originating mailer/Mailable, the acting user, and the send outcome) in a `mail_logs` table.

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

[](#requirements)

- PHP 8.3+
- Laravel 13

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

[](#installation)

```
composer require jiannius/mailog
php artisan migrate
```

> Not on Packagist yet — until it is, add a VCS repository to the host app's `composer.json`:
>
> ```
> "repositories": [
>     { "type": "vcs", "url": "https://github.com/jiannius/mailog" }
> ],
> "require": { "jiannius/mailog": "dev-main" }
> ```

The service provider auto-registers and the migration auto-loads, so `php artisan migrate`creates the `mail_logs` table. From that point **every outgoing email is logged** — `Mail::send`, Mailables, framework notifications, queued mail, all of it. No further wiring.

What gets logged
----------------

[](#what-gets-logged)

Each send creates one `Jiannius\Mailog\Models\MailLog` row:

ColumnContents`status``pending` → `sent` (a row left `pending` means the send failed)`mailer`the mailer name (e.g. `smtp`, `ses`)`mailable`the Mailable class, when sent via one`user_id` / `user_name`the resolved acting user + a name snapshot (see below)`from` / `to` / `cc` / `bcc` / `reply_to``[{address, name}]` arrays`subject`the subject line`html_body` / `text_body`the full rendered bodies`attachments``[{filename, mime, size}]` metadata (no file bytes)`tags` / `metadata`Symfony tag + metadata headers (`Mailable::tag()` / `metadata()`)`message_id`the transport message id, set once sent`sent_at`timestamp the transport accepted the messageA row is created `pending` when the message is about to send and flipped to `sent` once the transport accepts it. If the transport throws, the `sent` step never runs and the row stays `pending` — giving you failure visibility for free.

Querying logs
-------------

[](#querying-logs)

```
use Jiannius\Mailog\Models\MailLog;

MailLog::sent()->latest()->get();          // confirmed sent
MailLog::pending()->get();                 // in-flight or failed
MailLog::where('to', 'like', '%@acme.com%')->get();
```

`status` is cast to `Jiannius\Mailog\Enums\Status` (`PENDING`, `SENT`, `FAILED`). `FAILED` is never set automatically — it's reserved for you to set from a bounce/complaint webhook.

Configuration
-------------

[](#configuration)

Logging works with no config. To customise, publish the file:

```
php artisan vendor:publish --tag=mailog-config
```

```
// config/mailog.php
return [
    'enabled' => env('MAILOG_ENABLED', true),   // master switch
    'table'   => 'mail_logs',
    'except'  => [
        'mailers'   => [],   // mailer names to skip, e.g. ['log', 'array']
        'mailables' => [],   // Mailable classes to skip, e.g. [App\Mail\OrderShipped::class]
    ],
];
```

Opting out
----------

[](#opting-out)

Logging is on by default; opt specific email out three ways:

- **Master switch** — `MAILOG_ENABLED=false` (or `config('mailog.enabled')`) turns all logging off.
- **Config exclude** — list mailer names in `mailog.except.mailers` or Mailable classes in `mailog.except.mailables`.
- **Per-email** — add the `X-Mailog-Skip` header to a message. Mailog skips it and strips the header before the message is sent:

    ```
    use Jiannius\Mailog\Mailog;

    // in a Mailable's headers(), or via ->withSymfonyMessage(...)
    $message->getHeaders()->addTextHeader(Mailog::SKIP_HEADER, '1');
    ```

Attaching the acting user
-------------------------

[](#attaching-the-acting-user)

Each log records `user_id` + a `user_name` **snapshot** (so the log survives the user being deleted — there is no foreign key). By default the user is `auth()->user()`. Override the resolver in a service provider (cache-safe — never put a closure in a config file):

```
use Jiannius\Mailog\Mailog;

public function boot(): void
{
    // Return a model (uses getKey() + ->name), an ['id' => ..., 'name' => ...] array, or null.
    Mailog::resolveUserUsing(fn () => auth()->user());
}
```

Returning `null` (guests, queued jobs, console) leaves both columns null.

Custom columns (multi-tenant, etc.)
-----------------------------------

[](#custom-columns-multi-tenant-etc)

The `MailLog` model is unguarded, so you can attach your own columns — e.g. a `tenant_id` for a multi-tenant app:

1. Publish and edit the migration:

    ```
    php artisan vendor:publish --tag=mailog-migrations
    ```

    ```
    $table->ulid('tenant_id')->nullable()->index();
    ```
2. Feed the value through the data resolver in a service provider:

    ```
    use Jiannius\Mailog\Mailog;

    Mailog::resolveDataUsing(fn () => ['tenant_id' => tenant()->id]);
    ```

The returned array is filled onto the log, so the value lands in your real, indexed column: `MailLog::where('tenant_id', $id)->get()`.

How it works
------------

[](#how-it-works)

Mailog subscribes to Laravel's `MessageSending` and `MessageSent` events. The first creates the `pending` row and extracts every field from the Symfony `Email`; the second flips it to `sent`with the transport's message id. The two events are correlated by the message object itself (via a `WeakMap`), so logging is reliable even in long-running workers (Octane, `queue:work`).

Capture is wrapped so that **a logging failure can never break your application's email** — any exception while logging is reported and swallowed; the email still sends.

Development
-----------

[](#development)

This is a Composer library, so it has no `artisan` binary — Orchestra Testbench provides one (`vendor/bin/testbench` boots a throwaway Laravel app configured by `testbench.yaml`).

```
composer install
composer test                                   # Pest 4 + Testbench suite
vendor/bin/pest tests/Feature/MailLogTest.php   # one file
composer lint                                   # Pint
```

Tests run against in-memory sqlite and extend `Tests\TestCase` (wired automatically via `tests/Pest.php`).

License
-------

[](#license)

MIT.

###  Health Score

20

↑

LowBetter than 13% of packages

Maintenance65

Regular maintenance activity

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 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.

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/101174228?v=4)[Jiannius Technologies Sdn. Bhd.](/maintainers/jiannius)[@jiannius](https://github.com/jiannius)

---

Top Contributors

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

### Embed Badge

![Health badge](/badges/jiannius-mailog/health.svg)

```
[![Health](https://phpackages.com/badges/jiannius-mailog/health.svg)](https://phpackages.com/packages/jiannius-mailog)
```

###  Alternatives

[psr/log

Common interface for logging libraries

10.4k1.2B10.8k](/packages/psr-log)[open-telemetry/api

API for OpenTelemetry PHP.

1938.5M259](/packages/open-telemetry-api)[open-telemetry/sdk

SDK for OpenTelemetry PHP.

2326.5M315](/packages/open-telemetry-sdk)[illuminated/console-logger

Logging and Notifications for Laravel Console Commands.

8676.7k](/packages/illuminated-console-logger)

PHPackages © 2026

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