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 Database Emails Laravel Package

stackkit/laravel-database-emails

Store outgoing Laravel mail in your database instead of sending it. This package captures emails so you can inspect, preview, and debug them locally or in staging without external services or risking real deliveries.

View on GitHub
Deep Wiki
Context7

Getting Started

Version note (9.0.0): this release adds Laravel 13 support and drops Laravel 11. If you're still on Laravel 11, stay on ^8.0; upgrade Laravel first, then bump the package. There are no reported API changes in 9.0.0 — the major bump is purely the framework floor — but treat the upgrade as a "run the test suite and eyeball the config" step, since a new framework major can shift mailer/transport registration details.

Minimal setup:

composer require stackkit/laravel-database-emails
php artisan vendor:publish --tag="database-emails-config"
php artisan vendor:publish --tag="database-emails-migrations"
php artisan migrate

Then point your mailer at the package's transport in .env:

MAIL_MAILER=database

Clear cached config (php artisan config:clear) and send any mail — a Mail::to(...)->send(...), a notification, a password reset — and it lands in the emails table instead of going out over SMTP. The package registers a browsing route (enabled/configured in config/database-emails.php) where you can read the rendered HTML, inspect headers, and download attachments.

Where to look first: the published config/database-emails.php. It's short and it answers the questions you'll actually have — whether the UI route is registered, what middleware protects it, which filesystem disk holds attachments, and what the retention setting is. First real use case: wire it into local and staging so QA can click through real transactional emails (password resets, invoices, order confirmations) without a Mailtrap/SMTP dependency and without any risk of mailing a real customer.

Implementation Patterns

Environment-scoped swap (the common case). Keep MAIL_MAILER=database in .env.local / .env.staging and leave production on SES/Postmark. This is the lowest-friction pattern and requires zero application code. It also means every queued mailable, notification, and framework-generated email is captured for free.

Explicit secondary mailer (the safer production-adjacent case). Rather than replacing the default mailer, define a named mailer in config/mail.php that uses the database transport, then opt in per-send:

// config/mail.php
'database' => [
    'transport' => 'database',
],

// usage
Mail::mailer('database')->to($user)->send(new InvoiceReady($invoice));

This lets you keep real delivery for transactional mail while diverting, say, marketing or digest mail for inspection.

QA / support workflow. Capture → browse the rendered HTML → confirm the reset link, the currency formatting, the logo URL, the attachment filename. When a support ticket says "I never got the receipt," search the emails table by recipient and subject, confirm what was rendered, then use the package's resend action on the record rather than asking the user to retrigger the flow. This is where the package earns its keep over an SMTP sink.

Retention as a scheduled concern. Emails accumulate fast, and the body column is the bulk. The package's email model is set up for Laravel's pruning, so schedule php artisan model:prune in routes/console.php / the scheduler and set the retention window in config. Without this, staging databases grow to hundreds of MB of stale HTML.

Assertions in feature tests. For tests that need to verify rendered output rather than just Mail::fake() semantics, capture with the database mailer and assert against the stored record (subject, recipient, rendered body). Keep Mail::fake() for "was it sent" assertions and reserve the database mailer for "what did it look like" assertions — mixing the two in one test is confusing.

Gotchas and Tips

  • Silent success is the point and the trap. The transport never opens a socket, so nothing throws on a bad API key or blocked port. If mail "works" locally but not in production, the first question is always: which MAIL_MAILER does the production environment resolve, and is config cached?
  • Queued mail uses the worker's environment. A queue:work process started with a stale config cache — or a supervisor entry that pins a different --env — will happily use the wrong mailer. After changing MAIL_MAILER, run php artisan config:clear and restart workers.
  • Guard the browsing route. The UI exposes message bodies and attachments. Before enabling it anywhere non-local, confirm it sits behind auth (or better, a gate/can middleware) in the package config. Don't ship a route that lets anyone enumerate password-reset links.
  • Attachments can live outside the DB. Depending on config, attachment contents may be written to a filesystem disk with only metadata in the table. If downloads 404 after a deploy, check the configured disk exists on that host and survives releases (ephemeral container filesystems are a classic failure here).
  • Body size. Long HTML plus inline images makes wide rows. If the table gets slow, prune more aggressively or drop the html column retention — but do it deliberately, since the HTML is the debugging value.
  • Upgrading to 9.0.0: confirm your PHP constraint against the package's composer.json (framework majors usually move the PHP floor too), run migrations even though none are announced — a no-op is cheap — and re-publish the config only as a diff, not a overwrite, since your route middleware and retention values live there.
  • Extension points worth knowing: publishable views (so you can rebrand the browser UI if it's customer-facing), the config's route/middleware/disk/retention keys, and the underlying Email model — query it directly for custom dashboards or admin panels instead of building your own capture table.
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.
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
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core