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

React Email Laravel Package

maantje/react-email

Bring React Email to Laravel: build email templates as React components and render them server-side for use with Laravel Mailables. Combines JSX component ergonomics with Laravel’s mail workflow for maintainable, reusable transactional emails.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the PHP side.

    composer require maantje/react-email
    php artisan vendor:publish --tag="react-email-config"
    

    The config file is where the Node binary path, working directory (where your package.json lives), and rendering timeout are wired. Read it before writing any email — most setup failures trace back to these three values.

  2. Install the JS side. You need React and the React Email components in the project the package points at:

    npm i react @react-email/components
    

    Keep this node_modules present in every environment that will send mail (including production and queue workers), not just local.

  3. Create your first component. Put .tsx templates where your package.json / tsconfig expects them, e.g. resources/js/emails/Welcome.tsx:

    import { Html, Head, Body, Container, Text, Button } from '@react-email/components';
    type Props = { name: string; url: string };
    export default function Welcome({ name, url }: Props) {
      return (
        <Html><Head /><Body>
          <Container>
            <Text>Hi {name},</Text>
            <Button href={url}>Get started</Button>
          </Container>
        </Body></Html>
      );
    }
    
  4. Send it from a Mailable. The package exposes a mailable/helper that hands a component path plus props to the Node renderer. Confirm the exact method names against the README for your pinned version — the surface is small but has shifted across releases:

    class WelcomeEmail extends ReactEmailMailable
    {
        public function __construct(public User $user) {}
    
        public function build()
        {
            return $this->subject('Welcome aboard')
                ->react('emails/Welcome', [
                    'name' => $this->user->name,
                    'url'  => route('dashboard'),
                ]);
        }
    }
    
  5. First use case to ship: transactional mail only — password reset, welcome, receipts. Leave marketing/bulk campaigns on Blade until you've watched the renderer behave in production for a release cycle.

Implementation Patterns

  • Layout composition. Treat React Email's <Html>/<Body> as your layout component and nest per-template bodies. Create Emails/Layout.tsx once, export it, and import it in every template. This mirrors Blade layouts and is the single biggest quality-of-life win.
  • Props as your contract. Pass scalars and DTO-shaped arrays only. Compute URLs, dates (render them as strings — no Date objects crossing the bridge), and money formatting on the PHP side so the template stays presentational.
  • Preview loop. Run the renderer directly during development (the package typically ships a preview/render path — a route, a command, or a Mail::fake + assertSee). Whatever the mechanism, build a php artisan tinker one-liner that renders a component with fixture props so you can iterate on a .tsx file without sending mail.
  • Queueing. implements ShouldQueue works exactly as with Blade mailables. The renderer invocation happens in the queue worker process, so the worker must have Node, the correct working directory, and node_modules — a common "works in web, fails in queue" cause.
  • Notifications. Pass the mailable straight into Notification::toMail() → ->view() won't help; instead return a MailMessage that points at the React mailable, or compose the mailable into the notification's toMail() as a chained MailMessage. Test this path specifically — it's the easiest place to lose props.
  • Hybrid with Blade. There's no reason to convert everything. Keep a Blade fallback for the handful of mails ops needs to change quickly without a deploy, and use React Email for anything design-heavy.
  • Localization. Translate in PHP (__('mail.welcome.greeting', ['name' => …])) and pass the resolved strings as props. Rendering with the queue worker's locale is the trap you avoid here.

Gotchas and Tips

  • Production Node is mandatory. This package does not pre-compile emails into Blade. If your production image is php:8.3-fpm without Node, sending will fail at runtime. Bake Node + npm ci --omit=dev into your image, or pre-render to static HTML and store it.
  • Pin both sides. Composer pins the PHP package; package.json pins the JS runtime. Bump them together in a PR and render every template in CI. With ~50 stars and 0 dependents, treat any version bump as a change to be verified, not trusted.
  • Path resolution ignores tsconfig paths. The renderer resolves imports from its own entrypoint, so @/components/... aliases silently fail. Use relative imports inside templates, or set the renderer's working directory to the same root as your build.
  • Debugging renders. When a send throws an opaque error, reproduce outside Laravel: invoke the renderer's Node entrypoint directly with the same props. That separates "my React Email is broken" from "Laravel is handing it bad input" in one step.
  • No hooks, no async components. Server rendering here is a one-shot HTML string. useEffect, useState, and data fetching inside the template will not run (or will throw). Everything is props in, HTML out.
  • Timeout and memory config. Large templates or cold Node startups can exceed the default timeout. If mail queues stall, raise the timeout in the config before assuming a Symfony Mailer problem.
  • Images and assets. React Email can't magically inline your images. Host them at an absolute URL and remember many clients block remote images by default — include alt and avoid critical content in images.
  • Extension points to know. The config's working directory and Node binary are your levers for monorepos/Vapor/Docker. If you need a custom renderer, wrap the package behind your own ReactEmailService so a future swap (or the package going unmaintained) is a one-file change.
  • Maturity signal. 50 stars, no dependents, one release in early 2025. It's a good, focused package, but wrap it: one abstraction layer around rendering, plus a test that asserts a known string appears in a rendered fixture. That test is your canary for both your templates and the dependency.
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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums