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.
Architecture fit. FinMail is a Filament panel plugin, not a mail transport — it sits between your admin UI and Laravel's existing Mail facade. That is the correct architectural shape for a Laravel shop already on Filament: it adds a TemplateMail mailable, three admin resources (templates, themes, sent emails), a SendEmailAction that drops into any resource, and a SentEmailsRelationManager + HasEmailTemplates trait for per-model email history. The single universal Mailable replaces per-template classes, which is a real reduction in boilerplate and a meaningful shift in who owns email content — from engineers in Git to operators in a database.
That shift is the core TPM decision. It buys marketing/ops autonomy and per-locale content without deploys; it costs you code review, Git history, and staging parity for email copy. The package's versioning (auto-history, preview, one-click restore) partially compensates, but it is a UI-level undo, not a reviewed change-management workflow. Budget for an export/seed path so templates can be version-controlled and promoted between environments.
Integration feasibility. Installation is a single Composer require plus php artisan fin-mail:install, which publishes config/migrations, auto-detects locales from lang/, optionally registers the plugin, wires a custom Filament theme's Tailwind @source, configures Filament Shield permissions, and schedules log cleanup. Non-interactive mode (--locales=en,hu,de --seed) exists, so CI and scripted environments are supported — good sign of production intent.
Two friction points to plan for: (1) a known dependency conflict with phpdocumentor/reflection-docblock 6.x pulled in via spatie/laravel-settings, requiring an explicit -W pin of ^5.6 in the host app — this can force a transitive downgrade and needs a dependency-graph review before you commit; (2) spatie/laravel-settings introduces a settings table the installer may create, which is shared infrastructure you should own deliberately rather than let a package command create.
Technical risk. Ranked:
{{ model.rel.attr }}, {% if %}), conditionals, fallbacks, and merge tags are a bespoke parser, not Blade. Ask explicitly whether evaluation is a sandboxed parser or uses eval/extract-style constructs; verify escaping behavior for user-supplied values and whether nested relation traversal can trigger N+1 queries at send time.withoutStoringRenderedBody()), which the built-in password-reset/verification emails use because those bodies contain signed URLs. This is a PII/secrets surface in your database and backups; retention must be a deliberate policy, not a default.EditorContract to Tiptap/TinyMCE) and rendered through fin-mail::email.default. CTA blocks are inline-styled, but arbitrary editor HTML is not automatically Outlook-safe — validate against your client matrix before promising design quality.Key questions for the maintainers/vendor review.
{% if %} blocks handled — silent blank, or exception at dispatch?Stack fit. Strong, if and only if the host application is on Filament 4/5 with Laravel 11/12 and PHP 8.2+. Non-Filament or Livewire-only panels get little value — the entire management UX is Filament resources. Confirm this before spending evaluation time.
Migration path. There is no forced migration. Existing Mailables continue to work; TemplateMail is additive and can be adopted one send path at a time. Recommended sequence:
fin-mail:install --locales=… --seed non-interactively, inspect the emitted migrations and config/fin-mail.php, and resolve the phpdocumentor conflict against a copy of the real dependency graph.EmailTemplateService (send-by-key, models, locale, attachments) so the plugin is swappable. This is the single highest-value insurance policy given the maturity profile, and it costs hours, not days.withoutStoringRenderedBody().policyNamespace() option. Extend the built-in resources via emailTemplateResource()/sentEmailResource() rather than forking, and keep the built-in slugs so internal links resolve.SendEmailAction to one or two resources only after the pilot is stable; wire onSent callbacks to your own audit columns rather than relying on log entries alone.Compatibility. Pin the package version and treat Filament/Laravel major upgrades as gated: verify the package's supported matrix before upgrading the host app, not after. Note the custom-theme requirement (@source '../../../../vendor/finity-labs/fin-mail/resources/views/**/*') — if you use a custom Filament theme and the installer doesn't detect it, styles silently break.
Sequencing dependencies. Dependency-graph sign-off → install in staging → policy/role model → abstraction seam → pilot → logging retention job → resource actions → auth emails (optional). The role model and retention policy are prerequisites for production, not follow-ups; both are easier to define before editors start creating content.
Maintenance. Expect ongoing care, not a set-and-forget dependency. The realistic load is: monitoring releases and re-validating on each Filament/Laravel minor, plus owning the database surface the package creates. With 34 stars and a single maintainer org, assume you may eventually need to fork. The extension points — emailTemplateResource(), EditorContract, overrideView(), overrideBranding() — make a fork survivable, which materially reduces the risk. Budget roughly 1–2 engineer-days per quarter plus upgrade testing.
Support. GitHub issues only; no SLA, no vendor escalation. Internal ownership must be explicitly assigned — one engineer as package owner, one or two trained content editors. Documentation quality is genuinely above average for a package of this size (comprehensive README, changelog, release artifacts), and the installer's interactivity reduces setup ambiguity, but there is no community knowledge base to lean on.
Scaling. Three axes:
TemplateMail is automatically queued, so throughput is bounded by your queue workers, not the package. Logging writes one row per send at dispatch with Queued status, updated to Sent/Failed. At 10k sends/day that is 10k rows/day, and with rendered bodies stored those rows are large — the Text/JSON columns dominate table size. Enable the scheduled cleanup the installer offers and set retention explicitly.{{ order.customer.name }} can produce queries per email if models() isn't passed with already-loaded relations. Enforce eager loading at the call sites.Failure modes to instrument.
| Failure | Symptom | Mitigation |
|---|---|---|
| Queue worker down/backlogged | Logs stuck in Queued; users report no email |
Alert on Queued age > threshold; monitor queue depth |
| Bad token / null relation | Blank field or dispatch exception | Validate templates in preview before publish; test-send in CI-adjacent staging |
| Missing locale | Fallback or blank content for a locale | Verify per-locale fallback behavior; enforce required locales |
| Missing attachment path | Send fails or attachment silently dropped | Validate paths in attachments(); check file existence |
| Settings table absent | Runtime errors across resources | Treat fin-mail:install migration as a required deploy step, not interactive-optional |
| Rendered body retains secrets | Sensitive URLs in DB/backups | withoutStoringRenderedBody() on any signed-URL email; document as a code-review rule |
| Version restore clobber | Concurrent editor loses work | Confirm conflict behavior; prefer edit-freeze during restore |
| Theme CSS not scanned | Unstyled admin UI in custom themes | Verify @source line in theme CSS post-install |
Ramp-up. Two distinct audiences. Engineers: low — the API surface is small and idiomatic (TemplateMail::make()->models()->attachFile(), with()/extraData() for Blade-level variables). Content editors: moderate — the RichEditor is familiar, but the token syntax, merge tags, conditionals, CTA block, and per-locale editing in a single record are a custom mental model that needs a short written runbook and one live walkthrough. Plan a half-day of editor onboarding and a one-page "which template fires when" map, or you will get duplicate templates and unreviewed copy drift.
Recommendation. Adopt as a contained, reversible pilot behind an abstraction seam on non-critical transactional email; do not make it the system of record for authentication-critical mail in the first phase. The capability is real and well-scoped for a Filament shop, but the maturity signals (34 stars, 0 dependents, single maintainer, dual-major compatibility claims) mean the decision should be gated on the spike and the dependency-graph review, with a documented fork path in reserve.
How can I help you explore Laravel packages today?