Laravel Database Mail Templates

repository·main·Indexed 19 days ago

https://github.com/spatie/laravel-database-mail-templates

A Laravel package that allows rendering mailables using templates stored in the database instead of static files, enabling dynamic email content management without code deployments. It utilizes the Mustache templating engine and provides the TemplateMailable class to link mailables to database records, supporting custom layouts, multi-language integration via spatie/laravel-translatable, and custom template model resolution.

Tokens
3.6K
Snippets
12
Records
13
Agent score
66%

What's inside spatie/laravel-database-mail-templates

  1. Customize the MailTemplate model for multiple templates per mailable

    main

    The default MailTemplate model supports a 1:1 relationship between a mailable and a template. To support multiple templates for the same mailable (e.g., based on a user group or category), you should:

    1. Publish the mail_template migration.
    2. Create a custom model that extends \Spatie\MailTemplates\Models\MailTemplate and implements MailTemplateInterface.
    3. Override the scopeForMailable() method to define how the correct template is fetched.
    4. In your TemplateMailable class, set the $templateModelClass static property to your custom model.

    Example: A MeetupMailTemplate that fetches a template based on a meetup_group_id provided by the mailable.

    class MeetupMailTemplate extends MailTemplate implements MailTemplateInterface
    {
        public function scopeForMailable(Builder $query, Mailable $mailable): Builder
        {
            return $query
                ->where('mailable', get_class($mailable))
                ->where('meetup_group_id', $mailable->getMeetupGroupId());
        }
    }
    
    class NewMeetupPlannedMail extends TemplateMailable
    {
        protected static $templateModelClass = MeetupMailTemplate::class;
    
        public function getMeetupGroupId(): int
        {
            return $this->meetup->meetup_group_id;
        }
    }
  2. Use TemplateMailable to render templates from the database

    main

    To use database-stored templates, extend the \Spatie\MailTemplates\TemplateMailable class in your Laravel Mailable.

    By default, the package looks for a record in the mail_templates table where the mailable column matches your class name. All public properties on your Mailable class are automatically available as variables in your Mustache templates.

    If you need to pass data that isn't a public property, use the setAdditionalData() method in your constructor.

    namespace App\Mail;
    
    use Spatie\MailTemplates\TemplateMailable;
    
    class WelcomeMail extends TemplateMailable
    {
        /** @var string */
        public $name;
        
        public function __construct($user)
        {
            $this->name = $user->name;
            // For data not stored in a public property:
            $this->setAdditionalData([
                'extra_info' => 'some value'
            ]);
        }
    }
  3. Enable multi-language support for mail templates

    main

    The package does not support multi-language templates natively, but it is compatible with spatie/laravel-translatable.

    To enable it:

    1. Install spatie/laravel-translatable.
    2. Publish the create_mail_template_table migration.
    3. Modify the migration to change subject and html_template columns from text to json.
    4. Extend the MailTemplate model and use the HasTranslations trait, defining the translatable fields in the $translatable array.
    use Spatie\Translatable\HasTranslations;
    
    class MailTemplate extends \Spatie\MailTemplates\Models\MailTemplate
    {
        use HasTranslations;
        
        public $translatable = ['subject', 'html_template'];
    }
  4. Migrate from 2.x.x to 3.0.0

    main

    Upgrading from version 2.x.x to 3.0.0 requires database schema updates and changes to custom implementation interfaces.

    Database Changes

    The default migration has changed. You must manually update your database or create a new migration to apply these changes:

    1. Rename the template column to html_template.
    2. Add a new nullable text_template column.

    Custom MailTemplateModel Changes

    If you implement a custom model using MailTemplateInterface, the method signatures have changed to support text templates and avoid collisions with Laravel Nova. Replace the old methods with the new ones:

    • Replace subject() with getSubject()
    • Replace template() with getHtmlTemplate()
    • Add getTextTemplate()

    Custom TemplateMailable Changes

    If you are using a custom TemplateMailable, rename the $templateModel property to $templateModelClass.

  5. Add a header and footer using getHtmlLayout()

    main

    You can wrap your email body in a consistent layout (header/footer) by implementing the getHtmlLayout() method. This can be done in two places:

    1. In the TemplateMailable: Useful for layouts specific to a certain type of email.
    2. In the MailTemplate model: Useful for layouts that vary based on database properties (e.g., different branding for different clients).

    The method must return a string containing the {{{ body }}} placeholder. The package will inject the rendered template into this placeholder.

    Note for Blade users: If your layout is a Blade view, use the @{{{ body }}} syntax to prevent Blade from attempting to parse the Mustache placeholder.

    class WelcomeMail extends TemplateMailable
    {
        public function getHtmlLayout(): string
        {
            return '<header>Site name!</header>{{{ body }}}<footer>Copyright 2018</footer>';
        }
    }
    
    // Or via a Blade view:
    // return view('mailLayouts.main', $data)->render();
  6. Install laravel-database-mail-templates

    main

    Install the package via Composer and publish the migrations to set up the necessary database tables.

    1. Install the package:
    composer require spatie/laravel-database-mail-templates
    1. Publish the migrations:
    php artisan vendor:publish --provider="Spatie\MailTemplates\MailTemplatesServiceProvider" --tag="migrations"
    1. Run the migrations:
    php artisan migrate
    composer require spatie/laravel-database-mail-templates
    
    php artisan vendor:publish --provider="Spatie\MailTemplates\MailTemplatesServiceProvider" --tag="migrations"
    
    php artisan migrate
  7. Quick example: Sending a database-driven email

    main

    This example demonstrates how to create a MailTemplate record in the database and send it using a TemplateMailable.

    1. Define the template in the database:

      • mailable: The fully qualified class name of your TemplateMailable.
      • subject: The email subject line, supporting {{ variable }} syntax.
      • html_template: The HTML body content, supporting {{ variable }} syntax.
      • text_template: The plain text body content, supporting {{ variable }} syntax.
    2. Send the email: Use the standard Laravel Mail facade to send the mailable instance.

    // 1. Create the template record
    MailTemplate::create([
        'mailable' => \App\Mail\WelcomeMail::class,
        'subject' => 'Welcome, {{ name }}',
        'html_template' => '<p>Hello, {{ name }}.</p>',
        'text_template' => 'Hello, {{ name }}.'
    ]);
    
    // 2. Send the email
    Mail::to($user->email)->send(new WelcomeMail($user));
  8. Retrieve available template variables

    main

    To identify which variables are available for use in a Mustache template, you can call getVariables() on either the Mailable class or an instance of the MailTemplate model. You can also access the variables property on a MailTemplate instance.

    // From the Mailable class
    WelcomeMail::getVariables();
    
    // From a MailTemplate instance
    $template = MailTemplate::create(['mailable' => WelcomeMail::class, ...]);
    $template->getVariables();
    // or
    $template->variables;
  9. Create a TemplateMailable

    main

    To use database-stored templates, your Mailable class must extend Spatie\MailTemplates\TemplateMailable.

    You can define an HTML layout for your templates by implementing the getHtmlLayout() method. This method should return the string content of your layout (e.g., by reading a file from storage).

    Example implementation:

    namespace App\Mail;
    
    use Spatie\MailTemplates\TemplateMailable;
    
    class WelcomeMail extends TemplateMailable
    {
        /** @var string */
        public $name;
    
        public function __construct($user)
        {
            $this->name = $user->name;
        }
        
        public function getHtmlLayout(): string
        {
            $pathToLayout = storage_path('mail-layouts/main.html');
        
            return file_get_contents($pathToLayout);
        }
    }
    namespace App\Mail;
    
    use Spatie\MailTemplates\TemplateMailable;
    
    class WelcomeMail extends TemplateMailable
    {
        public $name;
    
        public function __construct($user)
        {
            $this->name = $user->name;
        }
        
        public function getHtmlLayout(): string
        {
            $pathToLayout = storage_path('mail-layouts/main.html');
        
            return file_get_contents($pathToLayout);
        }
    }
  10. Retrieve a MailTemplate for a specific Mailable

    main

    The MailTemplate model provides static and scope methods to find templates associated with a specific Laravel Mailable class. This is useful when you want to automatically fetch the database record that matches the mailable you are currently sending.

    • MailTemplate::findForMailable($mailable): Returns the first MailTemplate where the mailable column matches the class name of the provided $mailable. Throws a MissingMailTemplate exception if no template is found.
    • MailTemplate::forMailable($mailable): A query scope that allows you to further chain Eloquent queries when looking for templates for a specific mailable class.
    // Find the template for a specific mailable instance
    $template = MailTemplate::findForMailable($myMailable);
    
    // Or use the scope to build a query
    $templates = MailTemplate::forMailable($myMailable)->where('active', true)->get();
  11. Access MailTemplate content and metadata

    main

    The MailTemplate model exposes several methods to retrieve the data required to render an email. These methods access the underlying database columns:

    • getSubject(): Returns the email subject string.
    • getHtmlTemplate(): Returns the HTML body content.
    • getTextTemplate(): Returns the plain text body content (nullable).
    • getHtmlLayout(): Returns an optional HTML layout string (defaults to null).
    • getTextLayout(): Returns an optional plain text layout string (defaults to null).
    • getVariables(): Returns an array of variables required by the mailable. This method attempts to call getVariables() on the mailable class specified in the mailable column.
    $template = MailTemplate::first();
    
    $subject = $template->getSubject();
    $html = $template->getHtmlTemplate();
    $variables = $template->getVariables(); // Also accessible via $template->variables
  12. Render HTML, Text, and Subject via TemplateMailableRenderer

    main

    The TemplateMailableRenderer is responsible for rendering the different components of a mailable using templates stored in the database. It uses the Mustache templating engine to process the templates and injects data into them.

    Available Methods

    • renderHtmlLayout(array $data = []): string: Renders the HTML version of the template. It renders the HTML template body and then wraps it in an HTML layout.
    • renderTextLayout(array $data = []): ?string: Renders the plain text version of the template. If no text template is defined in the MailTemplate, it returns null (or the existing text view if available).
    • renderSubject(array $data = []): string: Renders the email subject line using the template's subject string and the provided data.

    Layout Resolution Logic

    When rendering a body (HTML or Text), the renderer looks for a layout using the following priority:

    1. A method on the TemplateMailable instance (getHtmlLayout() or getTextLayout()).
    2. A method on the MailTemplate model (getHtmlLayout() or getTextLayout()).
    3. A default fallback string: '{{{ body }}}'.

    Note: The layout MUST contain a placeholder for the body. If no valid placeholder is found, a CannotRenderTemplateMailable exception will be thrown.

    // Example usage concept
    $renderer = new TemplateMailableRenderer($templateMailable, $mustache);
    
    $html = $renderer->renderHtmlLayout(['name' => 'John Doe']);
    $subject = $renderer->renderSubject(['name' => 'John Doe']);