Skip to main content

Mail Templates

The Mail Templates module stores reusable mail-template definitions.

Install

Mail Templates depends on the Templates module. Install and enable Templates before installing Mail Templates.

composer require qore-next/mail-templates

The package auto-discovers MailTemplatesServiceProvider.

Publish migrations:

php artisan vendor:publish --tag=qore-next.mail-templates.db
php artisan vendor:publish --tag=qore-next.templates.db
php artisan migrate

The module also has publishable config:

php artisan vendor:publish --tag=qore-next.mail-templates.config

By default, sent mail messages are saved in mail_messages. Disable this with the Save mail messages checkbox in the module settings. Use Save mail attachments to also copy sent mail attachments into Qore's file system. This option is only shown when mail messages are being saved.

Data Model

MailTemplate stores:

  • name;
  • editable unique code_name, generated from name in the resource form;
  • required Twig-enabled subject;
  • optional comma-separated cc and bcc email recipients;
  • required template_id relation to a Template.

MailMessage stores sent mail history:

  • optional mail_template_id;
  • optional related notifiable model in entity_type and entity_id;
  • message id, recipients, subject, HTML body, text body and sent timestamp;
  • optional attachments as linked Qore File records when attachment saving is enabled.

MailMessageResource is read-only and is intended for sent-mail auditing. It shows the sent timestamp, subject, recipients, optional mail template, optional related entity, body previews and saved attachments.

Resource

Enable the module in Qore's modules UI. On enable it creates CRUDA permissions for mail_templates and view permissions for mail_messages; on disable it deletes both permission categories.

The resource includes a form/detail-only node field that links to the selected template detail page. Recipient fields use a tag-style Ant Design input and are stored as comma-separated strings.

When enabled, the module registers:

  • MailTemplatePolicy;
  • MailTemplateResource;
  • MailMessagePolicy;
  • MailMessageResource;
  • translations, config and migrations.

Register the frontend module in resources/js/app.tsx so the mail-template icon is loaded:

import mailTemplatesModule from '../../vendor/qore-next/mail-templates/resources/js'

<QoreAppProvider
app={{
// ...
modules: [mailTemplatesModule]
}}
/>

Add the template resources to app/Menu/AppMenu.php where you want them to appear:

$this->addResourceMenuItem(resource: qore()->getResourceOrFail('templates'));
$this->addResourceMenuItem(resource: qore()->getResourceOrFail('mail_templates'));
$this->addResourceMenuItem(resource: qore()->getResourceOrFail('mail_messages'));

Sending

Use MailTemplate::build() from a notification to render the mail template into a normal Laravel MailMessage. A useful pattern is to pass one small data object from the notification, then let the selected template variable set decide which structured values are exposed to Twig.

namespace App\Notifications;

use App\VariableSets\MyData;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Qore\Next\MailTemplates\Models\MailTemplate;

class SendTestNotification extends Notification
{
public function via(object $notifiable): array
{
return ['mail'];
}

public function toMail(object $notifiable): MailMessage
{
return MailTemplate::build(
codeName: 'wachtwoord_vergeten',
variableResolverInput: new MyData(
user: $notifiable,
someValue: 'Hello world',
),
);
}
}

build() renders both the mail-template subject and the linked template content with Twig. It also applies the template's cc and bcc recipients.

Use renderedContentTransformer when the final rendered subject or HTML must be changed before the MailMessage is returned. The transformer receives a RenderedMailTemplate after Twig variables are resolved, so it can be used for post-processing such as translation:

use Qore\Next\MailTemplates\RenderedMailTemplate;

return MailTemplate::build(
codeName: 'wachtwoord_vergeten',
variableResolverInput: new MyData(user: $notifiable, someValue: 'Hello world'),
renderedContentTransformer: function (RenderedMailTemplate $content) use ($translator) {
return $content
->setSubject($translator->translate($content->getSubject(), target: 'en'))
->setHtml($translator->translate($content->getHtml(), target: 'en'));
},
);

The transformed HTML is still passed to the selected mail Blade view as $html.

When save_mail_messages is enabled, every successfully sent Laravel mail is stored in mail_messages. Messages created through MailTemplate::build() also store the related mail_template_id. Notification mails sent to an Eloquent model notifiable also store the notifiable model as entity_type and entity_id. When save_mail_attachments is enabled, outgoing mail attachments are stored as Qore File records linked to the saved mail message with the file name attachments. When a transport provides an X-Qore-Actual-From header, that value is stored as the sender instead of the Symfony From header. The Microsoft Graph transport uses this to store the configured Graph mailbox rather than Laravel's default mail.from.address.

The module uses two Laravel events for this:

  • NotificationSending runs before Laravel sends a notification. It is only used to remember the notifiable Eloquent model for the notification mail.
  • MessageSent runs after any Laravel mail is successfully sent. It saves the final mail message, links the optional remembered notifiable model, and stores attachments when enabled.

The MyData object can be a small application class:

namespace App\VariableSets;

use App\Models\User;

class MyData
{
public function __construct(
public User $user,
public string $someValue,
) {}
}

When the linked template has a variable set, variableResolverInput is passed to each variable resolver. The resolved variable keys are then available in Twig. For example:

namespace App\VariableSets;

use Qore\Next\Templates\TemplateVariables\TemplateVariable;
use Qore\Next\Templates\TemplateVariables\TemplateVariableSet;

class MyTestSet extends TemplateVariableSet
{
public function label(): string
{
return 'My set';
}

public function code(): string
{
return 'my_set';
}

public function variables(): array
{
return [
TemplateVariable::make(
key: 'user.name',
resolver: fn (MyData $data) => $data->user->name,
),
TemplateVariable::make(
key: 'user.email',
resolver: fn (MyData $data) => $data->user->email,
category: 'User',
label: 'User e-mail address',
),
TemplateVariable::make(
key: 'some_value',
resolver: fn (MyData $data) => $data->someValue,
),
];
}
}

A variable with key user.name can be used in the subject and body as:

{{ user.name }}

You can also pass a list of resolver arguments:

return MailTemplate::build('welcome', [$notifiable, $invoice]);

By default, the rendered HTML is passed to the mail-templates::mail-template Blade view as $html. Pass mailView when a project needs a custom mail layout:

return MailTemplate::build(
codeName: 'welcome',
variableResolverInput: new MyData(user: $notifiable, someValue: 'Hello world'),
mailView: 'mail::custom-layout',
);