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

Filament Mail Log Laravel Package

asignua/filament-mail-log

A Filament plugin that records outgoing emails in your database and gives you a browsable log inside the admin panel, so you can inspect messages, recipients, and content without digging through server mail logs.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its config and migrations:

composer require <vendor>/<package>
php artisan vendor:publish --tag="<package>-config"
php artisan vendor:publish --tag="<package>-migrations"
php artisan migrate

As of v1.1.0 the migration set includes add_resent_from_id_to_mail_logs_table, which powers the new Resend action and its "Resent from #id" / "Resent N times" trail. Publish and run migrations on upgrade too — until that column exists, resend is hidden and logging behaves as before, which can look like a broken feature rather than a missing migration.

Register the plugin on the Filament panel where you want the preview UI:

use Vendor\Package\PackagePlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->plugin(PackagePlugin::make());
}

Then look at config/<package>.php first — that is where the redaction behavior lives, and now also the resend behavior. The keys that matter day-to-day are redaction.path_prefixes, redaction.patterns, and the new resend.enabled / resend.mailer / resend.queue.

The quickest first use case is still a password reset or e-mail verification flow. Trigger one in your local app, open the stored message in the Filament panel, and confirm that the token/hash is gone while the rest of the link remains readable. As of v1.0.1, /email/verify/{id}/{hash} renders as /email/verify/[REDACTED] rather than leaving the secret hash visible. Once that looks right, enable resend.enabled and grant the resendMailLog gate to one admin: resending the stored copy is the fastest way to learn the feature's limits, because the modal immediately tells you what redaction and truncation did to the body.

Implementation Patterns

Use redaction.path_prefixes for route-shaped secrets. Add entries for verification, password reset, signed URL, invitation, and download routes. The prefix matches a path and everything after it is replaced, so a single entry covers the whole tail of the URL:

'redaction' => [
    'path_prefixes' => [
        '/email/verify',
        '/password/reset',
        '/invitations',
        '/download',
    ],
],

Use redaction.patterns for query strings and free-form secrets. Path prefixes only cover paths, not query parameters. The package deliberately keeps ?email=... on reset links because it is personal data rather than a credential, so use a pattern if your retention policy requires dropping it, and add patterns for token=, signature=, key=, and similar parameters.

Treat resend as a separate privilege from viewing. Resend ships closed by default behind its own authorization surface — the resendMailLog gate or MailLogPlugin::authorizeResend(). Do not simply grant it to everyone who can see the log: being able to read a redacted preview is a very different risk from being able to send mail to arbitrary recipients from your domain. Start with a support/admin role and wire it through the plugin:

MailLogPlugin::make()
    ->authorizeResend(fn ($user) => $user->hasRole('support'));

Pick the resend mailer and queue deliberately. resend.mailer decides which mailer sends the stored copy, so it can diverge from your normal transactional path (and its DKIM/return-path/reputation setup). resend.queue moves the send onto the queue; keep it on for anything user-facing so the Filament request is not blocked by SMTP latency. Set both explicitly rather than relying on defaults, and verify the result with a real mailbox.

Model resend as an audit chain, not a mutation. Every resend is logged as a new row with resent_from_id pointing at the original; the message page surfaces "Resent from #id" and "Resent N times". Query the chain when investigating "did we ever resend this?", and make sure your retention/pruning job accounts for linked rows — pruning an original while keeping its descendants leaves dangling references and confusing counts.

Capture in all environments, redact per environment. A common split is to store messages in local/staging for debugging and either disable or shorten retention in production. Keep the redaction config identical across environments so a preview never behaves differently from what is stored.

Assert redaction in tests. After sending a notification, assert on the stored body that known secrets do not appear. This catches config regressions and new routes that were not added to path_prefixes:

$this->assertStringNotContainsString($user->verification_hash, $storedBody);
$this->assertStringContainsString('/email/verify/[REDACTED]', $storedBody);

Pair with a retention job. Redaction reduces exposure but does not remove personal data. Schedule pruning of stored messages and avoid rendering the preview to users who should not see another user's mail.

Use the workbench demo as a reference. It shows the expected Filament wiring, preview layout, and now the resend action/modal in context — the fastest way to see intended usage before adapting it to your panel.

Gotchas and Tips

The new migration is required for resend, but its absence is quiet. Without add_resent_from_id_to_mail_logs_table, the action is simply not shown and logging works as before — no error, no hint. If the Resend action is missing after upgrading, check migrations before you check permissions.

Resend sends the stored copy, not the original mail. Secrets removed by redaction, and bodies cut by truncation, are gone for good — the modal warns about this, and any signed or tokenized link in the resent mail will be broken. Resend is for content and delivery problems, not for "the user lost their password reset link"; use your normal notification flow for that.

Attachments are not resent. Only attachment names are stored. Do not promise a customer "we resent it with the invoice attached" — the invoice is not there.

Resend goes to arbitrary validated addresses, including Cc/Bcc. It is a mail-sending primitive on top of your domain. Keep the gate tight, keep the confirmation modal on, and consider whether the addresses are logged somewhere you are comfortable with.

Path-prefix semantics changed in v1.0.1. Previously only the first segment after the prefix was redacted, which meant /email/verify/[REDACTED]/{hash} kept the secret hash visible. It now removes every segment after the prefix. This is the important upgrade fix, not just cosmetic — audit any stored messages created before the upgrade, since those rows may still contain live verification hashes. Prune or re-redact them rather than assuming new code fixes old data.

Existing tests may need updating. If you asserted the old output shape ([REDACTED] followed by a visible hash), those assertions will now fail. Update them to expect the full path tail to be replaced.

The ?email= query is intentionally retained. Do not treat this as a bug. If your policy requires removing it, add an explicit pattern; otherwise it stays in the preview — and it now also stays in anything you resend.

Patterns are the sharp edge. A too-broad pattern will redact useful context and make previews hard to debug; a too-narrow one silently misses secrets. Add patterns alongside any new signed route or token format, and verify with a real message rather than reasoning about the regex.

Clear config cache after editing redaction or resend rules. php artisan config:cache in deploy pipelines will pin old rules; run php artisan config:clear locally after changes. The new resend.* keys are subject to the same trap.

The iframe width fix relies on inline styles. v1.0.1 replaced Tailwind utility classes — which Filament's compiled CSS does not include — with inline styles for light and dark mode. If you had a custom stylesheet compensating for the 300px-wide iframe, remove it, and check both themes after upgrading.

Check CSP and frame headers if the preview is blank. A restrictive frame-ancestors or X-Frame-Options on your app can block the preview iframe independently of the package.

Treat previews as sensitive. Redaction is best-effort defense in depth. Limit access to the Filament resource, keep retention short, and remember that anything not covered by a prefix or pattern is stored verbatim — and can now be mailed out again by anyone holding the resend permission.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource