mperonnet/symfony-react-email
Symfony integration for React Email that lets you render React-based email templates inside Symfony and send them via Symfony Mailer, bridging component-driven email design with Symfony services and configuration.
What it is (and isn't): mperonnet/symfony-react-email bridges React Email templates (JSX/TSX) into Symfony's Mailer stack. It is not a Laravel package — there's no service provider, no Mail:: facade integration. If you're a Laravel developer, treat this as a reference implementation of the rendering approach rather than a drop-in dependency; you'd be re-wiring it into a Laravel service provider or replicating the Node-render step yourself.
Minimal setup:
composer require mperonnet/symfony-react-email (pin the exact version — see Gotchas).package.json with @react-email/components and react, plus whatever entry script the bundle expects as its renderer binary.config/bundles.php (if it ships one) and check config/packages/ for the expected config key — with 3 stars and no docs site, open src/DependencyInjection/Configuration.php first. That file, not the README, is the source of truth for config keys, binary paths, and template directories.emails/welcome.tsx) that takes { name } and returns a <Html> tree.src/ for the class implementing rendering — likely named something like ReactEmailRenderer), call it with the template name and props to get HTML, then hand that to Symfony's MailerInterface with an Email instance.First use case worth shipping: a transactional welcome/reset-password email where the designer owns the JSX and you own the props contract. Keep it to one template until you've confirmed the render path works end-to-end in CI, because the failure mode (Node missing/binary mismatch) is indistinguishable from a template error at first glance.
Renderer-then-Mailer (the safe default). Treat the package as an HTML generator, not a mailer. Render to a string, then build your own Email/TemplatedEmail. This keeps you in control of headers, reply-to, and the plain-text alternative, and means a package upgrade can't silently change how mail is dispatched.
$html = $this->reactEmail->render('welcome', ['name' => $user->getName()]);
$email = (new Email())
->to($user->getEmail())
->subject('Welcome')
->html($html)
->text($this->plainTextFallback($user));
$this->mailer->send($email);
Wrap it in your own service. Put a thin App\Mail\EmailRenderer in front of the package so templates, subject lines, and the render call live in one place. When the package breaks or you outgrow it, you swap one class instead of grepping for render calls.
Async with Messenger — render at dispatch time. If mail is queued, decide deliberately: either render before dispatch and put the HTML on the message (simple, larger payloads, no Node in the worker), or pass template name + props and render in the handler (smaller payloads, but every worker now needs Node). For most teams the first is the right call; workers without a Node runtime are a common and painful surprise.
Props as a contract. Keep a PHP-side array shape per template and validate it (a small DTO with toArray()) rather than passing raw entities. React Email will happily render undefined into blank space and you'll only notice in production.
Dev preview loop. Add a dev-only controller route that renders a template with fixture props and returns the HTML. It collapses the React Email CLI preview and your Symfony app into one URL and makes designer review trivial.
Testing. Fake the renderer in unit tests ($this->reactEmail->method('render')->willReturn('<html>...</html>')) and keep one integration test that actually invokes Node against a real template. That single test is what catches "Node version drift broke the renderer" in CI.
config/mail.php yourself.src/ and the test suite are authoritative.dangerouslySetInnerHTML) bypasses it. Never pipe user-controlled content through that path.text() alternative. HTML-only mail hurts deliverability and fails accessibility checks; the package won't do this for you.How can I help you explore Laravel packages today?