glpzzz / form-mailer
Render a yiisoft form model to an email and send it with Symfony Mailer.
Requires
- php: >=8.2
- packaged/figlet: ^0.0.2
- symfony/event-dispatcher: ^7.4
- symfony/mailer: ^7.4
- symfony/mime: ^7.4
- symfony/twig-bridge: ^7.4
- twig/twig: ^3.10
- yiisoft/form-model: ^1.1
- yiisoft/validator: ^2.6
Suggests
- league/html-to-markdown: Better auto-generated plain-text part for templates without a .txt.twig sibling
- twig/cssinliner-extra: Enables {% apply inline_css %} for custom branded email templates
This package is auto-updated.
Last update: 2026-08-31 11:41:15 UTC
README
Render a yiisoft/form-model form to an
email and send it with Symfony Mailer. Replaces the hand-copied MailMessage
interface + EmailOutput trait + inline send wiring that TechniqueWeb sites
duplicated.
Install
composer require glpzzz/form-mailer
Use
Make the form mailable — implement MailableForm, add RendersFieldTable:
use Glpzzz\FormMailer\MailableForm; use Glpzzz\FormMailer\RendersFieldTable; use Glpzzz\FormMailer\Captcha\CaptchaRule; use Symfony\Component\Mime\Address; final class ContactForm extends FormModel implements RulesProviderInterface, MailableForm { use RendersFieldTable; public function getRules(): iterable { return [ // ... 'captcha' => [new Required(), new CaptchaRule('contact')], ]; } public function mailSubject(): string => "Quote request — {$this->name}"; public function mailReplyTo(): ?Address => new Address($this->email, $this->name ?? ''); public function mailTemplate(): string => '@form-mailer/table.html.twig'; // exclude the captcha field from the email protected function mailFields(): array => ['name', 'phone', 'email', 'comment']; protected function fieldType(string $n): string => match ($n) { 'email' => 'email', 'phone' => 'phone', default => 'text' }; }
Wire the mailer once and send after validation:
use Glpzzz\FormMailer\FormMailer; use Glpzzz\FormMailer\MailSettings; use Glpzzz\FormMailer\Captcha\FigletCaptcha; use Glpzzz\FormMailer\Captcha\SessionCaptchaStore; $captcha = new FigletCaptcha(new SessionCaptchaStore()); $mailer = FormMailer::create( dsn: $config['dsn'], settings: MailSettings::fromArray($config), // from, to, bcc, replyTo ); if ($form->isValid()) { $mailer->send($form); } $captcha->issue('contact'); // fresh code for the next render echo $captcha->banner('contact'); // figlet art for the template
Templates
@form-mailer/table.html.twig is the default: sans-serif, an <h1> subject,
and the field table with 0.25em cell padding and right-aligned labels. Nothing
else - no wrapper, colours, or web fonts.
For a different look, ship your own template and register the directory:
FormMailer::create(dsn: ..., settings: ..., templateDirs: [__DIR__ . '/emails' => 'app']);
{# @app/contact.html.twig - mailTemplate() returns '@app/contact.html.twig' #} {% apply inline_css %} {# needs twig/cssinliner-extra #} <style> /* ... */ </style> <img src="https://example.com/logo.png" alt="" height="40"> {{ include('@form-mailer/_table.html.twig', { fields }) }} {% endapply %}
Forms that aren't a label/value table
Skip RendersFieldTable, implement mailContext(): array by hand, and point
mailTemplate() at your own template. BodyRenderer renders any Twig template;
you still get FormMailer::send(), MailSettings and captcha.
What it renders
- HTML body from your Twig template.
- A plain-text part from the matching
*.txt.twig(the bundledtable.txt.twiggives a cleanLabel: valuelist); without one, a text part is derived from the HTML - crudely, or well ifleague/html-to-markdownis installed.