# Laravel Database Mail Templates This package enables Laravel applications to render email content from database-stored templates using Mustache templating syntax. It provides a flexible system for managing email templates dynamically, eliminating the need to redeploy code for email content changes. The package seamlessly integrates with Laravel's existing Mailable system while adding database-driven template management capabilities. The package supports both HTML and text email formats, custom layouts with header/footer wrapping, multi-tenant template management through custom models, and dynamic variable substitution using Mustache. It includes a migration for template storage, extensible interfaces for custom implementations, and integrates with Laravel's queueing system for background email processing. ## Creating a Basic Template Mailable Extend the TemplateMailable class to create mailables that render from database templates. All public properties are automatically available as template variables. ```php namespace App\Mail; use Spatie\MailTemplates\TemplateMailable; use App\Models\User; class WelcomeMail extends TemplateMailable { /** @var string */ public $name; /** @var string */ public $email; public function __construct(User $user) { $this->name = $user->name; $this->email = $user->email; } } // Create a database template use Spatie\MailTemplates\Models\MailTemplate; MailTemplate::create([ 'mailable' => \App\Mail\WelcomeMail::class, 'subject' => 'Welcome, {{ name }}!', 'html_template' => '

Hello, {{ name }}!

Your email is {{ email }}

', 'text_template' => 'Hello, {{ name }}! Your email is {{ email }}', ]); // Send the email use Illuminate\Support\Facades\Mail; $user = User::find(1); Mail::to($user->email)->send(new WelcomeMail($user)); ``` ## Setting Additional Template Data Use setAdditionalData() to inject variables that aren't defined as public properties on the mailable class. ```php namespace App\Mail; use Spatie\MailTemplates\TemplateMailable; use App\Models\User; use App\Services\RecommendationService; class PersonalizedMail extends TemplateMailable { public function __construct(User $user, RecommendationService $service) { $recommendations = $service->getRecommendationsFor($user); $this->setAdditionalData([ 'userName' => $user->name, 'topRecommendation' => $recommendations->first()->name, 'totalRecommendations' => $recommendations->count(), 'accountAge' => $user->created_at->diffForHumans(), ]); } } // Template can use these variables MailTemplate::create([ 'mailable' => \App\Mail\PersonalizedMail::class, 'subject' => 'Hi {{ userName }}, check out {{ topRecommendation }}', 'html_template' => '

We found {{ totalRecommendations }} items for you. Member since {{ accountAge }}.

', ]); ``` ## Adding HTML Layout with Header and Footer Override getHtmlLayout() to wrap templates in a consistent layout structure with header and footer elements. ```php namespace App\Mail; use Spatie\MailTemplates\TemplateMailable; class BrandedMail extends TemplateMailable { public $userName; public function __construct($userName) { $this->userName = $userName; } public function getHtmlLayout(): string { // From external file return file_get_contents(storage_path('mail-layouts/branded.html')); // Or from Blade view // return view('emails.layouts.branded')->render(); // Or inline // return '
My App
{{{ body }}}'; } public function getTextLayout(): string { return "MY APP\n----------\n{{{ body }}}\n----------\n© 2025 My Company"; } } // storage/mail-layouts/branded.html: // // // //
//

My Application

//
//
// {{{ body }}} //
// // // MailTemplate::create([ 'mailable' => \App\Mail\BrandedMail::class, 'subject' => 'Welcome {{ userName }}', 'html_template' => '

Hello {{ userName }}

Thanks for joining!

', ]); Mail::to('user@example.com')->send(new BrandedMail('John')); // Result: Full HTML with header, template body, and footer ``` ## Creating Custom Template Models Extend MailTemplate to support multi-tenant scenarios or add relationships and custom query scopes. ```php namespace App\Models; use Spatie\MailTemplates\Models\MailTemplate; use Spatie\MailTemplates\Interfaces\MailTemplateInterface; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Contracts\Mail\Mailable; class EventMailTemplate extends MailTemplate implements MailTemplateInterface { protected $table = 'event_mail_templates'; public function event(): BelongsTo { return $this->belongsTo(Event::class); } // Override query scope to filter by event public function scopeForMailable(Builder $query, Mailable $mailable): Builder { return $query ->where('mailable', get_class($mailable)) ->where('event_id', $mailable->getEventId()); } // Use event-specific layout public function getHtmlLayout(): string { return $this->event->email_layout_html; } } namespace App\Mail; use Spatie\MailTemplates\TemplateMailable; use App\Models\EventMailTemplate; use App\Models\Event; class EventNotificationMail extends TemplateMailable { protected static $templateModelClass = EventMailTemplate::class; public $eventName; public $eventDate; protected $event; public function __construct(Event $event) { $this->event = $event; $this->eventName = $event->name; $this->eventDate = $event->date->format('M d, Y'); } public function getEventId(): int { return $this->event->id; } } // Migration: php artisan make:migration create_event_mail_templates_table // Add: $table->foreignId('event_id')->constrained(); // Create templates per event $event1 = Event::find(1); EventMailTemplate::create([ 'event_id' => $event1->id, 'mailable' => \App\Mail\EventNotificationMail::class, 'subject' => 'Join us at {{ eventName }}', 'html_template' => '

{{ eventName }} on {{ eventDate }}

', ]); $event2 = Event::find(2); EventMailTemplate::create([ 'event_id' => $event2->id, 'mailable' => \App\Mail\EventNotificationMail::class, 'subject' => 'Don\'t miss {{ eventName }}', 'html_template' => '

Special event: {{ eventName }} - {{ eventDate }}

', ]); // Automatically uses correct template based on event Mail::to('attendee@example.com')->send(new EventNotificationMail($event1)); Mail::to('attendee@example.com')->send(new EventNotificationMail($event2)); ``` ## Retrieving Available Template Variables Access public properties from mailable classes to build template editors with available variable lists. ```php namespace App\Mail; use Spatie\MailTemplates\TemplateMailable; class OrderConfirmationMail extends TemplateMailable { public $orderNumber; public $customerName; public $totalAmount; public $itemCount; public function __construct($order) { $this->orderNumber = $order->number; $this->customerName = $order->customer->name; $this->totalAmount = $order->total; $this->itemCount = $order->items->count(); } } // Get variables from mailable class $variables = OrderConfirmationMail::getVariables(); // Returns: ['orderNumber', 'customerName', 'totalAmount', 'itemCount'] // Get variables from template model $template = MailTemplate::create([ 'mailable' => \App\Mail\OrderConfirmationMail::class, 'subject' => 'Order #{{ orderNumber }} Confirmed', 'html_template' => '

Hi {{ customerName }}, your {{ itemCount }} items totaling ${{ totalAmount }} are confirmed.

', ]); $variables = $template->getVariables(); // Returns: ['orderNumber', 'customerName', 'totalAmount', 'itemCount'] // Also available as attribute $variables = $template->variables; // Returns: ['orderNumber', 'customerName', 'totalAmount', 'itemCount'] // Build UI for template editor echo '
'; echo '

Available Variables

'; echo ''; echo ''; echo '
'; ``` ## Database Migration Setup Run migrations to create the mail_templates table for storing email template data. ```php // Publish migration php artisan vendor:publish --provider="Spatie\MailTemplates\MailTemplatesServiceProvider" --tag="migrations" // Migration creates this table structure: // database/migrations/2018_10_10_000000_create_mail_templates_table.php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateMailTemplatesTable extends Migration { public function up() { Schema::create('mail_templates', function (Blueprint $table) { $table->increments('id'); $table->string('mailable'); // Mailable class name $table->text('subject')->nullable(); // Email subject with variables $table->longText('html_template'); // HTML template body $table->longText('text_template')->nullable(); // Plain text version $table->timestamps(); }); } } // Run migration php artisan migrate // Seed templates use Illuminate\Database\Seeder; use Spatie\MailTemplates\Models\MailTemplate; class MailTemplatesSeeder extends Seeder { public function run() { MailTemplate::create([ 'mailable' => \App\Mail\WelcomeMail::class, 'subject' => 'Welcome to {{ appName }}', 'html_template' => '

Welcome {{ userName }}!

We\'re glad you joined.

', 'text_template' => 'Welcome {{ userName }}! We\'re glad you joined.', ]); MailTemplate::create([ 'mailable' => \App\Mail\PasswordResetMail::class, 'subject' => 'Reset your password', 'html_template' => '

Click here to reset: Reset Password

', 'text_template' => 'Reset your password: {{ resetUrl }}', ]); } } ``` ## Custom Mail Template Interface Implementation Implement MailTemplateInterface to create fully custom template storage or retrieval mechanisms. ```php namespace App\Models; use Spatie\MailTemplates\Interfaces\MailTemplateInterface; use Illuminate\Contracts\Mail\Mailable; class ApiMailTemplate implements MailTemplateInterface { protected $data; public static function findForMailable(Mailable $mailable): self { // Fetch template from external API $mailableClass = get_class($mailable); $response = Http::get("https://template-api.example.com/templates", [ 'mailable' => $mailableClass, ]); if ($response->failed()) { throw new \Exception("Template not found for {$mailableClass}"); } $instance = new static(); $instance->data = $response->json(); return $instance; } public function getSubject(): string { return $this->data['subject']; } public function getHtmlTemplate(): string { return $this->data['html']; } public function getTextTemplate(): ?string { return $this->data['text'] ?? null; } public function getHtmlLayout(): ?string { return $this->data['layout'] ?? null; } } namespace App\Mail; use Spatie\MailTemplates\TemplateMailable; use App\Models\ApiMailTemplate; class ApiBasedMail extends TemplateMailable { protected static $templateModelClass = ApiMailTemplate::class; public $userName; public function __construct($userName) { $this->userName = $userName; } } // Send email using API-sourced templates Mail::to('user@example.com')->send(new ApiBasedMail('John')); ``` ## Multilingual Template Support Integrate with Laravel's translatable packages to support multiple languages in email templates. ```php // composer require spatie/laravel-translatable // Modify migration to use JSON columns Schema::create('mail_templates', function (Blueprint $table) { $table->increments('id'); $table->string('mailable'); $table->json('subject')->nullable(); // JSON for translations $table->json('html_template'); // JSON for translations $table->json('text_template')->nullable(); // JSON for translations $table->timestamps(); }); namespace App\Models; use Spatie\MailTemplates\Models\MailTemplate as BaseMailTemplate; use Spatie\Translatable\HasTranslations; class MailTemplate extends BaseMailTemplate { use HasTranslations; public $translatable = ['subject', 'html_template', 'text_template']; } // Create multilingual templates MailTemplate::create([ 'mailable' => \App\Mail\WelcomeMail::class, 'subject' => [ 'en' => 'Welcome {{ name }}', 'es' => 'Bienvenido {{ name }}', 'fr' => 'Bienvenue {{ name }}', ], 'html_template' => [ 'en' => '

Welcome {{ name }}

Thanks for joining!

', 'es' => '

Bienvenido {{ name }}

¡Gracias por unirte!

', 'fr' => '

Bienvenue {{ name }}

Merci de nous rejoindre!

', ], ]); // Send localized email use Illuminate\Support\Facades\Mail; $user = User::find(1); Mail::to($user->email) ->locale($user->preferred_language) ->send(new WelcomeMail($user)); ``` ## Exception Handling Handle missing templates and invalid layout configurations with built-in exception types. ```php use Spatie\MailTemplates\Exceptions\MissingMailTemplate; use Spatie\MailTemplates\Exceptions\CannotRenderTemplateMailable; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Log; try { Mail::to('user@example.com')->send(new WelcomeMail($user)); } catch (MissingMailTemplate $e) { // No template exists in database for this mailable Log::error("Mail template missing: " . $e->getMessage()); // Could fall back to default template or notify admin // Create a default template MailTemplate::create([ 'mailable' => WelcomeMail::class, 'subject' => 'Welcome', 'html_template' => '

Default welcome message

', ]); } catch (CannotRenderTemplateMailable $e) { // Layout doesn't contain {{{ body }}} placeholder Log::error("Invalid mail layout: " . $e->getMessage()); // Fix layout or disable layout for this mailable } // Custom mailable with error handling namespace App\Mail; use Spatie\MailTemplates\TemplateMailable; class SafeMail extends TemplateMailable { public function getHtmlLayout(): string { // ❌ Invalid - missing body placeholder // return '
Header
'; // ✅ Valid layouts return '
Header
{{{ body }}}'; // or: '{{ body }}' // or: '{!! $body !!}' for Blade views // or: '@{{{ body }}}' for Blade views } } ``` ## Summary This package provides a comprehensive solution for managing Laravel email templates in a database, offering flexibility for content management without code deployments. It's ideal for applications requiring dynamic email content, multi-tenant systems with customized emails per tenant, or non-technical users managing email copy through an admin interface. The TemplateMailable base class integrates seamlessly with Laravel's mail system while the MailTemplate model stores subject lines and body content using Mustache syntax for variable interpolation. Common use cases include SaaS applications where each organization customizes their email branding and content, e-commerce platforms managing transactional emails (order confirmations, shipping notifications, abandoned cart reminders) without developer intervention, and marketing platforms enabling non-technical teams to A/B test email variations. The package scales from simple single-template scenarios to complex multi-tenant systems with custom models, layouts, and external template sources. Integration is straightforward: extend TemplateMailable, create database templates, and send emails using Laravel's standard Mail facade while benefiting from queueing, logging, and all other Laravel mail features.