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 Templates Laravel Package

wotz/filament-mail-templates

Filament Mail Templates is a Filament plugin for managing Laravel email templates directly from your admin panel. Create, edit, and organize reusable mail templates with dynamic variables, making transactional email updates faster and easier for your team.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and publish assets

composer require wotz/filament-mail-templates
php artisan vendor:publish --tag="filament-mail-templates-config"
php artisan vendor:publish --tag="filament-mail-templates-migrations"
php artisan migrate

Publish tags vary by package; if a tag is wrong, run php artisan vendor:publish interactively and pick the provider — that's the fastest way to see exactly what the package ships (config, migrations, views, translations).

2. Register the plugin on your panel

Filament plugins are registered in the panel provider, not the service provider:

// app/Providers/Filament/AdminPanelProvider.php
public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(\Wotz\FilamentMailTemplates\FilamentMailTemplatesPlugin::make());
}

If the class name differs, check the package README or composer show wotz/filament-mail-templates and look at the auto-discovered service provider to find the plugin/resource namespace.

3. Where to look first

  • The Filament Resource — this is the whole UI surface (list/create/edit form). Read its form schema to learn the exact field names for subject, body, and the variable/placeholder syntax it expects. Everything you do from application code must match those column names.
  • The config file (config/filament-mail-templates.php) — usually holds the model class, table name, editor driver, available placeholders, and mail-sending behaviour. This is your primary extension point.
  • The migration — tells you the real schema (columns, nullable, JSON casts) faster than any docs.

4. First use case: a welcome email

The canonical flow is:

  1. In the admin panel, create a template with a key/slug like welcome, a subject like Welcome, {{ user.name }}, and a body.
  2. Send it from a listener, controller, or job by resolving the template by key and passing data.
MailTemplate::query()
    ->where('key', 'welcome')
    ->firstOrFail()
    ->send(['user' => $user]);   // method name depends on the package API

Wire this into a Registered event listener first — it's the smallest end-to-end loop and immediately exposes any placeholder/rendering mismatches.


Implementation Patterns

Template-key indirection

The core value of a package like this is decoupling mail copy from code. Treat the template key as a contract:

  • Define keys as constants or an enum in your app (App\Mail\TemplateKey::Welcome).
  • Never hardcode the key string in more than one place.
  • Seed default templates in a seeder so fresh environments and tests aren't empty.
// database/seeders/MailTemplateSeeder.php
MailTemplate::updateOrCreate(
    ['key' => 'welcome'],
    [
        'subject' => 'Welcome, {{ user.name }}',
        'body'    => '<p>Hi {{ user.name }}, thanks for joining.</p>',
    ],
);

Use updateOrCreate keyed on the slug so re-seeding doesn't duplicate and so production edits survive deploys (see Gotchas).

A thin sending service

Wrap the package's send call in one app-level service. That gives you a single place to handle logging, fallbacks, locale selection, and queueing — and a single place to patch when the package API changes on upgrade.

class TemplateMailer
{
    public function send(TemplateKey $key, array $data, string|array $to): void
    {
        $template = MailTemplate::where('key', $key->value)->first();

        if (! $template) {
            report(new \RuntimeException("Missing mail template [{$key->value}]"));
            return;
        }

        $template->send($data, $to);
    }
}

Then call TemplateMailer from listeners, jobs, and Filament actions rather than touching the model directly.

Placeholder data as DTOs / arrays with a stable shape

Passing raw Eloquent models into the renderer works until a column is renamed. Prefer flattening to an explicit array:

$mailer->send(TemplateKey::Welcome, [
    'user' => [
        'name'  => $user->name,
        'email' => $user->email,
    ],
    'app' => ['name' => config('app.name')],
], $user->email);

Dot-notation placeholders ({{ user.name }}) usually resolve against nested arrays — this shape keeps them working.

Preview with sample data

Most of these packages ship a preview action on the resource. If yours does, wire its sample-data source to the same DTO shape you use in production, otherwise previews render blank {{ }} and editors lose confidence in the feature. If there's no preview action, add a custom Filament Action that renders the body with a fixture array and dumps it into a modal.

Filament admin workflow

  • Restrict the resource with a policy — mail templates are effectively code (they render HTML into outbound email), so limit edit access to trusted roles.
  • Add a key column to the table and make it searchable; without it, editors can't find templates.
  • Use Filament's RichEditor with a limited toolbar set if the package lets you swap the editor; full toolbars invite inline styles that break dark-mode and Outlook clients.
  • Consider a is_active/draft toggle so copy can be staged without going live.

Localization (if supported)

If the resource exposes a locale column, drive it from app()->getLocale() at send time and fall back to your default locale when the row is missing — otherwise a missing translation becomes a silent no-mail.

Queueing

Prefer queueing at the boundary rather than relying on the package:

dispatch(new SendTemplateMail($key, $data, $to));

This keeps web requests fast and gives you retries, which matters because template rendering failures (bad placeholder, missing relation) would otherwise throw during the user's request.


Gotchas and Tips

Very small install base (4 stars, no dependents) — pin it. Add an explicit constraint ("wotz/filament-mail-templates": "^1.0") and commit composer.lock. Expect API churn between minor versions; read the diff before composer update. Also verify the Filament major version it supports (v3 vs v4 panel providers differ meaningfully) before installing into an existing panel.

Seeder overwrites vs. production edits. If you seed templates on every deploy with updateOrCreate, you'll silently discard the copy your marketing team edited in the admin. Either seed only when the record is absent (firstOrCreate), or version the seed data and gate it behind a manual command.

Rendering and escaping. Two failure modes are common:

  • Placeholders are HTML-escaped, so {{ user.name }} containing & renders as &amp; — usually fine, but confusing when the raw HTML editor shows the entity.
  • The body is rendered as raw HTML (by design, since it's an email body). That means any user-supplied variable interpolated into the body is an injection vector. Never pass unescaped user input into placeholders that land in the body; escape or strip at the DTO boundary.

Missing or extra placeholders fail silently. A {{ user.nmae }} typo typically renders as an empty string, not an exception. Defensive steps:

  • Add a test that sends every seeded template with a full fixture array and asserts no {{ remains in the rendered subject/body.
  • Log the resolved data array (redacted) in the sending service during development.

Config cache. After changing the config file or registering the plugin, run php artisan config:clear (and optimize:clear in CI/deploy). Filament resource/plugin registration also caches route and component discovery — php artisan filament:clear-cached-components (or optimize:clear) fixes "resource not showing up after install".

Migrations on existing databases. Check the published migration for the table name before running — collisions with an existing mail_templates table are a real risk if you've hand-rolled one. If you already have one, either map the package to your table via config or write a data-migration rather than letting the package create a second table.

Panel route/permission leakage. If the plugin auto-registers on all panels (or you forget ->plugin() in a non-admin panel but the routes still resolve), the template editor may be reachable by users who shouldn't see it. Verify with php artisan route:list | grep -i mail-template and add a policy.

Extension points to prefer over forking:

  • Config: model class, table name, editor component, placeholder registry, and (often) the mailer used for dispatch. Override the model via config rather than subclassing the resource.
  • Custom model: extend the shipped model to add tenant_id, locale, or is_active, then point the config at yours.
  • Filament resource extension: if the package exposes it, extend the resource and override form()/table() to add fields — cleaner than editing vendor files.
  • Views: publish the package views if the rendered email wrapper needs your branding (header/footer, unsubscribe link). This is almost always necessary before going live.

Testing. Use Mail::fake() and assert a Mail/Mailable was sent with the expected subject after rendering; also assert the rendered body for one template end-to-end. Since the package is young, an integration test that actually renders and sends each template is your best upgrade safety net.

Low-activity maintenance risk. With 4 stars and no dependents, treat this as a small internal dependency: budget for reading the source (it's likely a few classes), and keep your TemplateMailer wrapper thin enough that replacing the package with a hand-rolled model + Blade renderer is a half-day job, not a rewrite.

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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast