finity-labs/fin-mail
FinMail for Filament is an email template manager and composer. Build, manage, and send emails from your admin panel with token replacement, multilingual templates, themes, versioning, email logging, and a reusable Send Email action.
composer require plus a day of configuration. The decision is less "is this package perfect" and more "is templated transactional email a differentiator for us" — for the vast majority of SaaS and internal-tool products, it is not.TemplateMail::make('x', locale: 'hu')) mean "support German/Spanish/Japanese emails" becomes a content task rather than an engineering project. This directly de-risks international expansion: you can commit to new markets without a per-locale template engineering workstream.TemplateMail::make('slug') call, adding a new lifecycle email (order shipped, trial ending, invoice overdue) becomes content + a one-line trigger rather than a new Mailable class, view, and test. This materially lowers the cost of "add another notification" items on the roadmap and makes them schedulable in a single sprint alongside other work.SendEmailAction, SentEmailsRelationManager, Shield policies, nav control) rather than a bolt-on third-party UI. That constrains the decision usefully: the value is high because of the Filament context, not independent of it.withoutLogo(), overrideBranding([...])) let you support partner co-branding or white-label variants without forking views — a real enabler if co-marketing or reseller motions are on the roadmap.Decision this most clearly answers: "Should we build an in-admin email template manager, or adopt one?" — Adopt, if you are on Filament and your needs are transactional (not campaign) email.
Adopt when most of these are true:
phpdocumentor/reflection-docblock vs. spatie/laravel-settings conflict. Budget half a day for install friction rather than assuming a clean composer require.Look elsewhere when any of these are true:
{% if %} conditionals cover typical cases; anything resembling loops, computed totals, or rich formatting logic should be passed in via with()/extraData() and kept in code. If your emails need heavy logic, that logic does not belong in a WYSIWYG.Two caveats to schedule as explicit review items, not footnotes:
withoutStoringRenderedBody() exists), but you should still decide your retention policy up front — the install command offers scheduled cleanup of old sent emails, and you should turn it on with a defined window rather than defaulting to "forever."TemplateMail is queued automatically and logged at dispatch with a Queued status, flipping to Sent/Failed after the worker runs. This is a good default, but it means your queue workers become part of the email critical path — confirm queue monitoring and failed-job alerting exist before launch.For executives — the one-paragraph version: Transactional email is currently a developer task in our product: every copy change, every new notification, every new language is an engineering ticket and a deploy. This package moves that work into the admin panel, where marketing and support can do it themselves — with version history so nothing is unrecoverable, and a log of everything we've sent so support can answer "what did we tell this customer?" It is MIT-licensed and free, it plugs directly into the admin panel we already run, and the integration is roughly a day of engineering plus a day of hardening. We should treat it as a small, reversible bet: low cost, immediate reduction in engineering interruptions, and it unblocks our localization work without a separate project.
For engineering — the technical case:
TemplateMail class replaces N per-template Mailables and Blade views. New transactional emails become TemplateMail::make('slug')->models([...]) — no new class, no new view, no new test scaffold.SendEmailAction drops into any resource's table/header actions; HasEmailTemplates + SentEmailsRelationManager gives per-record send history in two lines. Shield policies and nav config are handled.EditorContract for swapping the editor (RichEditor default, Tiptap/TinyMCE supported), resource classes are extendable and replaceable via the plugin, views are publishable, overrideView() per email, with()/extraData() for real Blade variables when tokens aren't enough.withLogging(), withoutLogging(), withoutStoringRenderedBody()).spatie/laravel-settings transitive dependency and a documented phpdocumentor resolution conflict; a custom Filament theme needs an @source line added (the installer attempts this). Test and style CI badges are green; a changelog and tagged releases exist.For the risk-averse stakeholder — the honest framing: This is a young, single-maintainer package (34 stars, no known dependents). That is a real risk and we should not pretend otherwise. Three things make it acceptable: it is MIT-licensed so we can fork or patch it ourselves; it renders into standard Laravel mail, so removal is a contained migration rather than a rewrite; and it replaces code we would otherwise write and maintain ourselves — meaning the counterfactual is not "zero risk," it is "we own this code forever." Our mitigation is to pin the version, add an integration test around our three highest-value emails, and revisit the fork decision if the package goes 6+ months without a release.
Ask: Approve a timeboxed spike — one engineer, two days, installing against a staging panel and reproducing three real emails (including one translated, one with an attachment, and one with a conditional block). Success criteria: templates editable in-admin, sends logged and linked to their source record, and no unresolved dependency conflicts. On pass, adopt for transactional email only and keep campaign email out of scope.
How can I help you explore Laravel packages today?