codedor/filament-mail-templates
codedor/filament-mail-templates is a Filament plugin that brings mail template management into your Laravel admin panel, letting you create, edit, and organize email templates through a simple, user-friendly interface built for Filament.
wotzebra/filament-mail-templates is a Filament plugin for creating and editing multilingual email templates from within your admin panel. The v2.2.0 release is a minor update that adds compatibility with translatable-tabs v3 and bumps CI dependencies — no breaking changes to the plugin's own API.
Minimal steps:
Install the package alongside Filament and the translatable-tabs dependency:
composer require wotzebra/filament-mail-templates
Since v2.2.0 targets translatable-tabs v3, confirm your composer.json resolves to the v3 line, or pin the plugin to 2.1.* if you must stay on translatable-tabs v2.
Publish and run the migrations for the templates table:
php artisan vendor:publish --tag="filament-mail-templates-migrations"
php artisan migrate
Optionally publish the config to override the model, table name, or default locales:
php artisan vendor:publish --tag="filament-mail-templates-config"
Register the plugin in your panel provider:
public function panel(Panel $panel): Panel
{
return $panel
->plugin(MailTemplatesPlugin::make());
}
Look here first: the auto-registered MailTemplateResource in your Filament panel is the entry point. Create your first template there, then wire it into a Laravel Mailable or Notification.
First use case: add a "Welcome Email" template with subject/body content, translate it across your configured locales using the tabbed editor, then render it from a Mailable so non-developers can edit copy without deployments.
Template resolution and rendering. The typical pattern is to look up a template by a stable key (slug or type) and render its subject and body with the current locale, falling back to your app.fallback_locale:
$template = MailTemplate::forKey('welcome')->firstOrFail();
return (new WelcomeMail($template->translate(app()->getLocale())))
->to($user);
Keep the key stable — it is your contract between code and editable content.
Blade rendering of user-edited content. Render the template body through Blade (or a sandboxed renderer) so editors can use variables like {{ $user->name }}. Always pass a well-defined, documented variable bag; do not pass whole models implicitly unless you control the template.
Locale tabs workflow. With translatable-tabs v3, the resource shows one tab per locale. Encourage editors to fill all locales; if a locale is empty, your fallback logic determines what actually ships. A common pattern is to enforce required subject/body on the fallback locale via form validation rules in a custom resource subclass.
Overriding the resource. Extend the package's resource to add fields, permissions, or navigation grouping:
class MailTemplateResource extends \Wotzebra\FilamentMailTemplates\Resources\MailTemplateResource
{
public static function getNavigationGroup(): ?string { return 'Communications'; }
}
Register the override in your panel; the plugin respects model/config substitution.
Mailables and Notifications. Two integration styles work well:
Mailable class per logical email, pulling subject/body from the template. Better for typed variables and testing.Notifications. For notifications, resolve the template inside toMail() and set ->subject() and ->line()/->view() from the rendered content.
Seeding and environments. Seed default templates in a seeder keyed by slug so migrate:fresh gives you a working baseline. Keep seeding idempotent (updateOrCreate on the key) so it doesn't clobber edits in non-local environments.
Testing. Use Mail::fake() and assert against the resolved template content, not hard-coded strings, so translations and editor changes don't break tests.
translatable-tabs v3 alignment is the main upgrade concern. v2.2.0 adds support for translatable-tabs v3, but it does not force it. Run composer why-not wotzebra/filament-mail-templates 2.2.0 after upgrading to confirm your dependency graph resolves cleanly; a stale v2 constraint elsewhere can silently keep the plugin on 2.1.x.
Locale fallback is not automatic everywhere. Depending on your config, translate() may return null for untranslated locales rather than falling back. Wrap lookups or configure a fallback explicitly; otherwise users with unconfigured locales receive blank emails.
Cache your resolved templates. Template reads on every mail send are cheap but avoidable. Cache rendered subject/body per (key, locale) and invalidate on save via the model's saved event — the plugin updates templates outside deployments, so route caching alone is insufficient.
Debug emails in isolation. Use Mail::fake(), then Mail::assertSent and inspect the mailable's rendered HTML with $mailable->render(). For real-send debugging, set MAIL_MAILER=log and read storage/logs/laravel.log — you'll see the fully rendered template and can verify tab/locale behavior instantly.
Sanitize or sandbox Blade. User-edited bodies rendered through Blade can execute arbitrary PHP if editors are untrusted. Either restrict template editing to trusted roles, or render through a sandboxed Blade environment / Twig-style engine.
Watch the variable contract. Because content is editable, renaming a variable in code silently breaks templates. Document the variable bag per template key and validate on render (throw a clear exception when a required variable is missing) to catch this early.
Publishing assets conflicts on upgrade. Re-publishing migrations or config on upgrade can overwrite local customizations. Prefer config overrides in config/filament-mail-templates.php over editing published files, and check vendor/wotzebra/filament-mail-templates/config/ after each upgrade for new keys.
Extension points. Model override, resource override, and config-driven table/model names are the supported customization surface. For deeper changes (e.g., custom tabs, custom subject field), extend the resource and re-register it; avoid editing vendor files.
Dependency-bump transparency. This release also bumps actions/checkout from 6 to 7 — CI-only, no runtime impact. The substantive change is the translatable-tabs v3 support; treat v2.2.0 as a safe minor upgrade unless you pin translatable-tabs for other reasons.
How can I help you explore Laravel packages today?