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 fromnamein the resource form; - required Twig-enabled
subject; - optional comma-separated
ccandbccemail recipients; - required
template_idrelation to aTemplate.
MailMessage stores sent mail history:
- optional
mail_template_id; - optional related notifiable model in
entity_typeandentity_id; - message id, recipients, subject, HTML body, text body and sent timestamp;
- optional attachments as linked Qore
Filerecords 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:
NotificationSendingruns before Laravel sends a notification. It is only used to remember the notifiable Eloquent model for the notification mail.MessageSentruns 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',
);