Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Email Database Log Laravel Package

dmcbrn/laravel-email-database-log

Log every email your Laravel app sends straight to the database. Captures recipients, subject, HTML and text bodies, headers, and send status for easy debugging, auditing, and resending, with configurable logging rules and a queryable Eloquent model.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a small, single-purpose package: it hooks Laravel's mail events and writes every outgoing message into an email_logs table. There is essentially no API to learn — the value is in getting it installed cleanly and deciding where it should run.

Minimal install:

composer require dmcbrn/laravel-email-database-log
php artisan vendor:publish --provider="Dmcbrn\LaravelEmailDatabaseLog\LaravelEmailDatabaseLogServiceProvider"
php artisan migrate

If a config file is published, read it before migrating — the main decisions (attachment handling, connection) are usually there. If not, defaults live in the service provider.

Verify it works with one line:

Mail::raw('hello from the log test', fn ($m) => $m->to('[email protected]')->subject('Log test'));

Then check email_logs in your DB, or:

\Dmcbrn\LaravelEmailDatabaseLog\EmailLog::latest()->first();

Where to look first:

  1. vendor/dmcbrn/laravel-email-database-log/src/LaravelEmailDatabaseLogServiceProvider.php — this is the entire package. It registers a listener on Laravel's mail events and defines the publishable migration. Read it before trusting anything else.
  2. The published migration — confirms the schema (from/to/cc/bcc, subject, body, headers, attachments).
  3. The EmailLog model — confirms table name, connection, and whether any scopes exist.

First real use case: local/staging development. Instead of running Mailhog/Mailtrap just to answer "what did we actually send that user?", you get a queryable record in SQL you can inspect with Tinker:

EmailLog::latest()->first()->body;

Implementation Patterns

Zero-touch global logging. The package works purely through package discovery + an event listener. Once installed, anything sent through Laravel's mailer (Mail::send, Mail::raw, notifications, Mailable classes) is logged automatically. There are no call sites to modify — that is the whole appeal.

A lightweight "sent mail" admin view. A common pattern is a small read-only controller so non-developers can confirm what was emailed:

Route::get('/admin/email-logs', function () {
    return view('email-logs.index', [
        'logs' => EmailLog::latest()->paginate(25),
    ]);
})->middleware('can:view-email-logs');

Filter by recipient when debugging a support ticket:

EmailLog::where('to', 'like', '%[email protected]%')->latest()->get();

Local-only install in production. Since the package auto-registers, the cleanest way to keep it off production is dont-discover plus manual registration:

"extra": {
  "laravel": {
    "dont-discover": ["dmcbrn/laravel-email-database-log"]
  }
}
// AppServiceProvider::register()
if ($this->app->environment('local', 'staging')) {
    $this->app->register(\Dmcbrn\LaravelEmailDatabaseLog\LaravelEmailDatabaseLogServiceProvider::class);
}

Alternatively composer require --dev if you never need it in production, but note the migration will then live in a dev dependency — fine for most teams, awkward if you later want it in staging.

Feature-test assertions. Handy when you want to assert a Mailable was actually composed and sent (as opposed to Mail::fake(), which short-circuits delivery entirely):

$this->post('/register', [...]);
$this->assertDatabaseHas('email_logs', ['to' => '[email protected]']);

Only do this in a test suite with a disposable DB — the bodies are large.

Retention. These rows grow fast and contain full message bodies. Schedule a prune:

$schedule->call(
    fn () => EmailLog::where('created_at', '<', now()->subDays(30))->delete()
)->daily();

Pairing with a real catcher. In local dev, Mailhog still wins for visual rendering. The DB log wins for "what did the queue worker actually send at 03:12 last night?" and for anything that already left the machine. Running both is normal.

Gotchas and Tips

Body and headers are stored raw and may be MIME-encoded. What you pull out of $log->body is often quoted-printable or otherwise encoded. Don't assume it's the HTML you saw rendered:

$body = quoted_printable_decode($log->body);
// or decode per the stored Content-Transfer-Encoding header

Budget time for this the first time you try to display a log entry in a UI.

Mail::fake() means no logs. Faking swaps the mailer for one that records in memory and never dispatches delivery, so the package's listener never fires. If a test asserts on email_logs and uses Mail::fake(), it will mysteriously find nothing. Pick one strategy per test.

Queued mail logs at send time, not dispatch time. Mail::queue(...) returns immediately; the row appears when the worker actually hands the message to the transport. Debugging "the email isn't in the log" starts with php artisan queue:work / failed jobs, not with the package.

Storage blowup is the real risk. Every message body, HTML and text and headers, lands in a LONGTEXT column. A moderately busy app can add hundreds of MB per month. Decide on truncation or a short retention window before enabling this in staging with real traffic. Attachments metadata is cheap; attachment contents are usually not stored at all — verify in the migration.

PII and content sensitivity. You are now holding password-reset links, invoices, magic login URLs, and personal correspondence in a plain table. Restrict any UI behind authorization, exclude the table from broad DB dumps, and treat it with the same care as your users table.

Check whether it listens on MessageSending or MessageSent. This determines failure semantics: one fires before the transport call, the other after success. If it's MessageSending, a log row does not prove the email was accepted by the SMTP server. Read the provider's boot() method once and write that fact down — it changes how you interpret the table when diagnosing bounces.

config:cache can mask configuration changes. If there is a published config file, remember to php artisan config:clear after editing it in local development. Standard Laravel trap, but easy to lose ten minutes to when the "attachment logging off" flag appears to do nothing.

Custom connection / multi-tenant setups are not handled. The model uses the default DB connection. If you run per-tenant databases or a separate logging database, you'll need to override the model's $connection or subclass it — that's your main extension point, along with swapping out the model if you want scopes for filtering.

Version compatibility is your job, not the package's. At 16 stars and a single-maintainer cadence, don't assume a new Laravel major is supported on day one. Check the composer.json constraints before upgrading the framework.

Consider whether this should be a dependency at all. The entire mechanism is roughly one event listener and one migration. If your needs are modest and you're nervous about a low-traffic package in your dependency tree, copying ~60 lines into app/Listeners/LogSentEmail.php gives you full control over encoding, retention, connection, and which fields you store. The honest assessment: adopt it for a fast start, and be prepared to inline it if it ever falls behind a Laravel release.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource