Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Symfony React Email Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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:

  1. composer require mperonnet/symfony-react-email (pin the exact version — see Gotchas).
  2. Add the Node side: a package.json with @react-email/components and react, plus whatever entry script the bundle expects as its renderer binary.
  3. Register the bundle in 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.
  4. Build a trivial template (emails/welcome.tsx) that takes { name } and returns a <Html> tree.
  5. Send it: inject the renderer service (look in 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.

Implementation Patterns

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.

Gotchas and Tips

  • This is a 3-star, 0-dependent package released once (March 2025). Pin the exact version, and seriously consider vendoring the renderer class into your own codebase. The MIT license makes that trivial and it removes a single-maintainer dependency from your mail path.
  • Node is a runtime dependency of a PHP app. That's the central operational cost: your Docker image, CI, and any queue worker that renders must carry Node plus the JS deps. Document it in the README or the next person will deploy a broken mailer.
  • No Laravel support. The prompt's framing aside, there's no service provider/facade. In Laravel you'd bind the renderer in a service provider and resolve the binary path from config/mail.php yourself.
  • Verify the API against source, not the README. With this adoption level, method names and config keys may have drifted from any docs. src/ and the test suite are authoritative.
  • Escaping. JSX auto-escapes interpolated props, which is good — but anything you pass as raw HTML (dangerouslySetInnerHTML) bypasses it. Never pipe user-controlled content through that path.
  • Asset URLs. Email clients need absolute URLs. If templates reference images or fonts by relative path, they'll break in the inbox but look fine in dev preview — render with an explicit base URL.
  • Always ship a text() alternative. HTML-only mail hurts deliverability and fails accessibility checks; the package won't do this for you.
  • Failure modes are opaque. A missing binary, a JS exception, and a malformed prop all surface as roughly the same error. Log the raw stderr from the Node process; without it, debugging is guesswork.
  • Extension point: the renderer class is your seam. Anything the package doesn't expose (caching rendered templates, batching renders, injecting a custom Node command) belongs in a decorator around it rather than a fork — unless you've already decided to vendor, in which case just edit it.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager