Looking to hire Laravel developers? Try LaraJobs

laravel-mail-templates maintained by colibri-informatique

Description
Admin-editable transactional email templates with safe rendering, delivery logs, preview and an optional Filament UI.
Last update
2026/11/15 22:42 (dev-main)
License
Links
Downloads
0

Comments
comments powered by Disqus

Laravel Mail Templates

Modèles d’emails transactionnels éditables depuis le back-office, avec rendu sûr, journal de livraison, prévisualisation et interface Filament optionnelle.

Ce package est né du projet Plateforme de l’Engagement et a été conçu pour être réutilisé tel quel sur d’autres applications Laravel, notamment un futur projet de crowdfunding / crowdlending.


Pourquoi un package dédié ?

Le marché Laravel contient déjà plusieurs briques intéressantes. La revue faite avant extraction :

Package Forces Limite pour notre besoin
spatie/laravel-database-mail-templates (447★, MIT) Modèles en base, Mustache, layouts, package maintenu Orienté Mailable : pas de journal de livraison, pas de back-office, pas de synchronisation de définitions, pas de blocage explicite d’un modèle inactif
visualbuilder/email-templates (118★, GPL-3.0) Éditeur d’emails pour Filament Licence GPL, édition seulement : pas de journal ni de cycle de vie
TappNetwork/filament-maillog (30★, MIT) Journal des emails dans Filament Journal uniquement, pas de modèles éditables
jeffersongoncalves/laravel-mail (11★, MIT) Très large : templates, tracking, webhooks, suppression, statistiques Jeune, faible adoption, périmètre très large et dépendances lourdes
simplepleb/laravel-email-templates (121★, GPL-3.0) Templates Blade Licence GPL, projet arrêté en 2023, pas de journal
dansmaculotte/laravel-mail-template (35★, MIT) Driver de templates Projet arrêté en 2022, périmètre limité
Laravel Notifications Livraison multi-canaux native Mécanisme d’envoi, pas de gestion de modèles ni de journal

Le module SilverStripe lekoala/silverstripe-email-templates a servi d’inspiration pour plusieurs idées :

  • modèles identifiés par un code stable plutôt que par une classe ;
  • import/synchronisation de modèles par défaut sans écraser les personnalisations ;
  • journal des emails envoyés avec corps conservé ;
  • nettoyage périodique des journaux ;
  • aperçu et métadonnées (catégorie, variables disponibles, activation/désactivation).

Ce package reprend ces idées, mais reste dans l’écosystème Laravel et utilise Mustache (comme Spatie) plutôt qu’un moteur de template maison.


Fonctionnalités

  • Modèles stockés en base, identifiés par un code stable (auth.magic-link, loan.payment.reminder, etc.).
  • Rendu Mustache : variables échappées, sections {{#if variable}}...{{/if}} supportées pour compatibilité.
  • Refus des variables non échappées ({{{ ... }}}, {{& ... }}) : pas d’injection HTML via une donnée métier.
  • Aucun rendu Blade du contenu édité : un administrateur ne peut pas exécuter du PHP.
  • Modèle absent ou inactif : l’envoi est bloqué et une ligne de journal skipped est créée. Aucun contenu de repli n’est envoyé silencieusement.
  • Journal complet : queued, sending, sent, failed, skipped, tentatives, message-id, erreur, corps rendu, contexte.
  • Envoi en file avec retries/backoff configurables.
  • Prévisualisation et envoi de test depuis le back-office.
  • Commande de synchronisation non destructive des modèles par défaut.
  • Commande de purge selon une politique de rétention.
  • Interface Filament 5 optionnelle (liste, édition, aperçu, test, journal).
  • Traductions FR/EN et vues publiables.
  • Aucun couplage au domaine métier : le package ne connaît ni Mission, ni Participation, ni Loan.

Installation

Depuis un dépôt local / VCS

{
    "repositories": [
        {
            "type": "path",
            "url": "../laravel-mail-templates",
            "options": { "symlink": true }
        }
    ]
}
composer require colibri-informatique/laravel-mail-templates:@dev

Le service provider est auto-découvert. Les migrations sont chargées automatiquement ; pour les personnaliser :

php artisan vendor:publish --tag=mail-templates-config
php artisan vendor:publish --tag=mail-templates-migrations
php artisan migrate

Autres publications :

php artisan vendor:publish --tag=mail-templates-views
php artisan vendor:publish --tag=mail-templates-css
php artisan vendor:publish --tag=mail-templates-lang

Commande introuvable : mail-templates:sync

Si Laravel répond The command "mail-templates:sync" does not exist, le service provider n’a pas été chargé. Vérifiez dans cet ordre :

# 1. Le package est-il réellement installé ?
composer show colibri-informatique/laravel-mail-templates
ls vendor/colibri-informatique/laravel-mail-templates/src/MailTemplatesServiceProvider.php

# 2. La découverte des packages est-elle à jour ?
php artisan package:discover
php artisan optimize:clear

# 3. La commande apparaît-elle ?
php artisan list | grep mail-

Causes fréquentes :

  • installation avec composer install --no-scripts ou composer require --no-scripts, qui empêche package:discover de s’exécuter ;
  • bootstrap/cache/packages.php obsolète (le supprimer puis relancer php artisan package:discover) ;
  • package listé dans extra.laravel.dont-discover du projet hôte ;
  • commande lancée depuis un autre répertoire que la racine Laravel.

En dernier recours, enregistrez explicitement le provider.

Laravel 11, 12 et 13 — bootstrap/providers.php :

use ColibriInformatique\MailTemplates\MailTemplatesServiceProvider;

return [
    App\Providers\AppServiceProvider::class,
    MailTemplatesServiceProvider::class,
];

Laravel 10 — config/app.php, clé providers :

'providers' => [
    // ...
    ColibriInformatique\MailTemplates\MailTemplatesServiceProvider::class,
],

Aucune définition de modèle

Le message :

Aucune définition de modèle. Renseignez mail-templates.templates ou un provider.

signifie que le package est bien chargé, mais que l’application n’a déclaré aucun modèle. Le package ne livre volontairement aucun contenu par défaut : mail-templates:sync ne crée que les modèles déclarés par le projet.

Diagnostic :

php artisan mail-templates:doctor

La commande indique si la configuration est publiée, combien de modèles sont déclarés, si chaque provider est trouvable et si les tables existent.

Cas 1 — déclaration dans la configuration

php artisan vendor:publish --tag=mail-templates-config
php artisan config:clear

Puis, dans config/mail-templates.php du projet (jamais dans vendor/) :

'templates' => [
    'auth.magic-link' => [
        'label' => 'Lien de connexion',
        'category' => 'Authentification',
        'variables' => [
            'name' => 'Nom du destinataire',
            'url' => 'Lien de connexion',
        ],
        'subject' => 'Votre lien de connexion',
        'body' => '<p>Bonjour {{ name }}</p><p><a href="{{ url }}">Se connecter</a></p>',
        'preview' => [
            'name' => 'Camille',
            'url' => 'https://example.test/magic/abc',
        ],
    ],
],

Vérification :

php artisan config:clear
php artisan mail-templates:doctor
php artisan mail-templates:sync --dry-run

Cas 2 — déclaration via un provider

Vérifiez les trois points suivants :

  1. la classe est autoloadable :

    composer dump-autoload
    
  2. elle implémente bien ColibriInformatique\MailTemplates\Contracts\TemplateProvider ;

  3. elle est déclarée dans la configuration du projet :

    // config/mail-templates.php
    'providers' => [
        App\Mail\AppMailTemplates::class,
    ],
    

Puis :

php artisan config:clear
php artisan mail-templates:doctor

Depuis la version actuelle, un provider introuvable, un mauvais contrat ou une définition invalide lève une exception explicite au lieu d’être ignoré silencieusement.

Pour inspecter la configuration réellement chargée :

php artisan tinker
config('mail-templates.providers');
config('mail-templates.templates');
app(\ColibriInformatique\MailTemplates\Registry\TemplateRegistry::class)->all();

Déclarer les modèles attendus

Via la configuration

// config/mail-templates.php
'templates' => [
    'auth.magic-link' => [
        'label' => 'Lien de connexion',
        'category' => 'Authentification',
        'variables' => [
            'name' => 'Nom du destinataire',
            'url' => 'Lien de connexion',
        ],
        'subject' => 'Votre lien de connexion',
        'body' => '<p>Bonjour {{ name }}</p><p><a href="{{ url }}">Se connecter</a></p>',
        'preview' => [
            'name' => 'Camille',
            'url' => 'https://example.test/magic/abc',
        ],
    ],
],

Une variable peut aussi être décrite avec un exemple :

'variables' => [
    ['name' => 'amount', 'label' => 'Montant', 'example' => '1 250,00 €'],
],

Via un provider

use ColibriInformatique\MailTemplates\Contracts\TemplateProvider;
use ColibriInformatique\MailTemplates\Definitions\TemplateDefinition;
use ColibriInformatique\MailTemplates\Definitions\TemplateVariable;

final class AppMailTemplates implements TemplateProvider
{
    public function definitions(): iterable
    {
        yield new TemplateDefinition(
            code: 'loan.payment.reminder',
            label: 'Relance de paiement',
            category: 'Prêts',
            variables: [
                new TemplateVariable('first_name', 'Prénom'),
                new TemplateVariable('amount', 'Montant', '250,00 €'),
                new TemplateVariable('due_date', 'Date d’échéance', '30/11/2026'),
            ],
            subject: 'Échéance à venir le {{ due_date }}',
            body: '<p>Bonjour {{ first_name }},</p><p>Votre échéance de {{ amount }} arrive le {{ due_date }}.</p>',
            preview: [
                'first_name' => 'Camille',
                'amount' => '250,00 €',
                'due_date' => '30/11/2026',
            ],
        );
    }
}
// config/mail-templates.php
'providers' => [
    App\Mail\AppMailTemplates::class,
],

Synchroniser les modèles

# Crée les modèles manquants, inactifs, avec le contenu par défaut
php artisan mail-templates:sync

# Crée et active les modèles manquants
php artisan mail-templates:sync --activate

# Écrase aussi les modèles existants (attention aux personnalisations)
php artisan mail-templates:sync --force

# Vérifie sans écrire
php artisan mail-templates:sync --dry-run

# Limite à certains codes
php artisan mail-templates:sync --only=auth.magic-link,loan.payment.reminder

Choix assumé : un modèle synchronisé reste inactif par défaut. Un email ne part jamais sans validation explicite du contenu.


Envoyer un email

use ColibriInformatique\MailTemplates\Services\MailDispatcher;

$log = app(MailDispatcher::class)->queue(
    code: 'auth.magic-link',
    to: $user->email,
    data: [
        'name' => $user->full_name,
        'url' => $url,
    ],
    context: [
        'user_id' => $user->id,
    ],
);

if ($log->status === \ColibriInformatique\MailTemplates\Enums\MailLogStatus::Skipped) {
    // Le modèle est absent ou inactif : l’envoi a été volontairement bloqué.
}

Envoi synchrone (tests, back-office) :

app(MailDispatcher::class)->sendNow('auth.magic-link', $email, $data);

Envoi de test, même si le modèle est inactif :

app(MailDispatcher::class)->sendTest('auth.magic-link', 'admin@example.test');

Cycle de vie du journal

Statut Signification
queued Le mailable est en file d’attente.
sending Le transport a commencé l’envoi (événement MessageSending).
sent Le transport a accepté le message (MessageSent).
failed Échec définitif du job ou exception d’envoi.
skipped Modèle absent ou inactif : aucun email n’a été envoyé.

Le journal est mis à jour via les en-têtes X-Mail-Template-Code et X-Mail-Template-Log-Id, ce qui évite de désérialiser le payload du job et reste robuste aux évolutions de Laravel.

Purge :

php artisan mail-logs:prune
php artisan mail-logs:prune --days=30
php artisan mail-logs:prune --dry-run

Le corps des emails peut contenir des données personnelles. La rétention par défaut est de 90 jours ; adaptez-la à votre politique RGPD.


Interface Filament

Ajoutez le plugin à votre panel :

use ColibriInformatique\MailTemplates\Filament\MailTemplatesPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(MailTemplatesPlugin::make());
}

L’interface apporte :

  • Modèles d’emails : liste, édition de l’objet et du contenu, activation, aide sur les variables, aperçu, envoi de test.
  • Journal des emails : liste filtrable, page de détail, aperçu du corps, erreurs et message-id.

Le corps des emails est affiché dans une iframe sandboxée : le CSS de l’email ne peut pas casser l’interface Filament et les scripts éventuellement présents dans le HTML ne sont pas exécutés.

Les libellés sont traduits en français et en anglais. Le groupe de navigation est configurable via mail-templates.ui.navigation_group.

L’interface ne crée pas de modèles : les codes sont définis par le développeur. Utilisez mail-templates:sync pour créer les lignes manquantes.


Sécurité du rendu

  • Le contenu édité est rendu par Mustache, jamais par Blade.
  • {{ variable }} est toujours échappé (htmlspecialchars).
  • Les formes non échappées sont refusées par défaut :
    • {{{ variable }}}
    • {{& variable }}
  • Vous pouvez les autoriser explicitement avec allow_unescaped_variables, mais ce n’est pas recommandé.
  • Les variables inconnues sont rendues vides, comme avec Mustache.
  • Un modèle inactif ne produit aucun envoi de repli.
  • Le contenu HTML reste édité par un administrateur de confiance ; limitez l’accès à l’écran Filament aux rôles appropriés (le package ne fournit pas de policy par défaut).

Configuration

Clé Défaut Rôle
templates [] Définitions déclaratives.
providers [] Classes TemplateProvider.
table_names.templates mail_templates Table des modèles.
table_names.logs mail_logs Table des journaux.
layout mail-templates::layout Vue de layout.
css null Chemin CSS personnalisé ; sinon CSS du package.
inline_css true Inline le CSS dans le HTML final.
allow_unescaped_variables false Autorise {{{ ... }}} et {{& ... }}.
queue.tries 5 Tentatives du mailable.
queue.backoff [10,30,60,300,900] Délais entre tentatives.
logs.store_body true Conserve le corps rendu dans le journal.
logs.retention_days 90 Rétention par défaut de mail-logs:prune.
ui.enabled true Active les écrans Filament.
ui.navigation_group Administration Groupe de navigation Filament.

Tests

composer install
composer test
composer lint
composer phpstan

Le package utilise Orchestra Testbench, Pest, SQLite en mémoire et Mail::fake().


Migration depuis un existant

Le package a été extrait d’une application qui utilisait :

  • un enum EmailTemplateCode avec 22 cas ;
  • un EmailService et un TemplateEmail ;
  • des tables email_templates et email_logs ;
  • des écouteurs de queue dans AppServiceProvider.

Pour migrer :

  1. Déclarer les 22 modèles dans un TemplateProvider (ou dans la config).
  2. Remplacer les appels EmailService::queue() par MailDispatcher::queue().
  3. Supprimer les écouteurs JobProcessing / MessageSent / JobExceptionOccurred de l’application : le package les gère via les en-têtes du message.
  4. Transférer les contenus existants vers mail_templates (un import legacy est prévu en v0.2).
  5. Supprimer les anciens modèles, migrations, vues et ressources Filament.

Les notifiers métier (ParticipationCancelledNotifier, etc.) restent dans l’application : ils appellent simplement le dispatcher.


Feuille de route

  • v0.2 : commande d’import depuis une table legacy, versioning des modèles, modèles par locale.
  • v0.3 : adaptateur Laravel Notifications (TemplateNotification), canal SMS, relances idempotentes.
  • v0.4 : policies Filament, permissions par rôle, aperçu MJML éventuel.

Le package reste volontairement séparé d’un futur laravel-notifications : les templates et le journal sont une capacité stable, les relances et les canaux pourront s’appuyer dessus.


Licence

MIT.