# 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' => '
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 AppThanks 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 '{{ {$variable} }}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' => 'Thanks for joining!
', 'es' => '¡Gracias por unirte!
', 'fr' => '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 '