mscode-pl / laravel-react-email
Build Laravel mailables with react-email
Installs: 377
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Language:TypeScript
pkg:composer/mscode-pl/laravel-react-email
Requires
- php: ^8.1|^8.2|^8.3|^8.4|^8.5
- illuminate/contracts: ^10.0|^11.0|^12.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- orchestra/testbench: ^7.20 || ^8.0 || ^9.0 || ^10.0 || ^11.0
- pestphp/pest: ^1.20 || ^2.0 || ^3.0
README
Build Laravel email templates using react-email components. Variables from your Mailable are injected at send-time via Laravel Blade — no static strings hardcoded into HTML.
How it works
resources/react-email/welcome-email.tsx ← you write this
↓ php artisan react-email:build
resources/views/react-email/welcome-email.blade.php ← generated Blade template
↓ Mail::to($user)->send(new WelcomeEmail(...))
email with real data injected by Blade at send-time
The build step compiles your TSX to static HTML and replaces placeholder props with Blade expressions:
| In template | In compiled Blade |
|---|---|
$$name$$ |
{{ $name }} |
$$user.email$$ |
{{ $user['email'] }} |
blade.number('price') + {price.toFixed(2)} |
{{ $price }} |
<BladeForEach items="items"> |
@foreach($items as $item) |
<BladeIf condition="fee > 0"> |
@if($fee > 0) |
Installation
1. Composer
composer require mscode-pl/laravel-react-email
2. Publish config
php artisan vendor:publish --provider="MsCodePL\LaravelReactEmail\LaravelReactEmailServiceProvider" --tag="react-email-config"
3. npm dependencies
Install react-email and link the library from your vendor directory so TSX templates can import its helpers:
npm install react react-email @react-email/components tsx
Then add to your project's package.json:
{
"dependencies": {
"@react-email/components": "^1.0.1",
"@mscode-pl/laravel-react-email": "file:vendor/mscode-pl/laravel-react-email"
}
}
Run npm install once more to create the symlink:
npm install
Why? The package lives in
vendor/(Composer only, not published to npm). Thefile:reference letstsxresolveimport { blade } from '@mscode-pl/laravel-react-email'correctly.
Quick start
Generate a Mailable + template
php artisan make:react-email WelcomeEmail
Creates:
app/Mail/WelcomeEmail.phpresources/react-email/welcome-email.tsx
Write the template
resources/react-email/welcome-email.tsx:
import React from 'react'; import { Html, Head, Body, Container, Text, Button } from '@react-email/components'; import { blade } from '@mscode-pl/laravel-react-email'; interface WelcomeEmailProps { userName?: string; activationUrl?: string; } export default function WelcomeEmail({ userName = '$$userName$$', activationUrl = '$$activationUrl$$', }: WelcomeEmailProps) { return ( <Html> <Head /> <Body style={{ fontFamily: 'Arial, sans-serif' }}> <Container> <Text>Welcome, $$userName$$!</Text> <Text>Click below to activate your account:</Text> <Button href={activationUrl}>Activate Account</Button> </Container> </Body> </Html> ); }
Build
php artisan react-email:build
Send
app/Mail/WelcomeEmail.php:
use MsCodePL\LaravelReactEmail\ReactMailable; use Illuminate\Mail\Mailables\{Content, Envelope}; class WelcomeEmail extends ReactMailable { public function __construct( public string $userName, public string $activationUrl, ) {} public function envelope(): Envelope { return new Envelope(subject: 'Welcome!'); } public function content(): Content { return new Content( html: 'react-email.welcome-email', text: 'react-email.welcome-email-text', ); } }
Mail::to($user)->send( new WelcomeEmail( userName: $user->name, activationUrl: route('activate', $user->activation_token), ) );
Variables
Simple string variables
Use $$variableName$$ directly in JSX text. The build step converts them to {{ $variableName }}.
export default function InvoiceEmail({ invoiceNumber = '$$invoiceNumber$$', dueDate = '$$dueDate$$', }) { return ( <Html> <Text>Invoice #$$invoiceNumber$$</Text> <Text>Due: $$dueDate$$</Text> </Html> ); }
Compiled Blade output:
<p>Invoice #{{ $invoiceNumber }}</p> <p>Due: {{ $dueDate }}</p>
Nested object variables
Dot notation is converted to array access:
export default function OrderEmail({ user = { name: '$$user.name$$', email: '$$user.email$$' }, shop = { name: '$$shop.name$$' }, }) { return ( <Html> <Text>Hello $$user.name$$,</Text> <Text>Your order at $$shop.name$$ is confirmed.</Text> <Text>Confirmation sent to $$user.email$$</Text> </Html> ); }
Compiled Blade output:
<p>Hello {{ $user['name'] }},</p> <p>Your order at {{ $shop['name'] }} is confirmed.</p> <p>Confirmation sent to {{ $user['email'] }}</p>
Numeric variables
Use blade.number('varName') so methods like .toFixed() work at build time without crashing:
import { blade } from '@mscode-pl/laravel-react-email'; export default function OrderEmail({ subtotal = blade.number('subtotal'), total = blade.number('total'), }) { return ( <Html> <Text>Subtotal: {subtotal.toFixed(2)} PLN</Text> <Text>Total: {total.toFixed(2)} PLN</Text> </Html> ); }
Compiled Blade output:
<p>Subtotal: {{ $subtotal }} PLN</p> <p>Total: {{ $total }} PLN</p>
Blade handles the actual number formatting at send-time with
number_format($total, 2)etc.
Lists / arrays — <BladeForEach>
Use the <BladeForEach> component to loop over an array. Inside, write $$item.property$$ placeholders:
import { BladeForEach } from '@mscode-pl/laravel-react-email'; export default function OrderEmail() { return ( <Html> <Text>Your items:</Text> <BladeForEach items="items"> <Section> <Text>$$item.name$$ × $$item.quantity$$ — $$item.price$$ PLN</Text> </Section> </BladeForEach> </Html> ); }
Compiled Blade output:
@foreach($items as $item) <div> <p>{{ $item['name'] }} × {{ $item['quantity'] }} — {{ $item['price'] }} PLN</p> </div> @endforeach
For nested arrays (e.g. order.items):
<BladeForEach items="order.items"> <Text>$$item.name$$ — $$item.price$$ PLN</Text> </BladeForEach>
Compiles to:
@foreach($order['items'] as $item) <p>{{ $item['name'] }} — {{ $item['price'] }} PLN</p> @endforeach
Conditional sections — <BladeIf>
Use <BladeIf> to wrap content that should only appear when a condition is true:
import { BladeForEach, BladeIf, blade } from '@mscode-pl/laravel-react-email'; export default function OrderEmail({ total = blade.number('total'), serviceFee = blade.number('serviceFee'), }) { return ( <Html> <BladeForEach items="items"> <Text>$$item.name$$ — $$item.price$$ PLN</Text> </BladeForEach> <BladeIf condition="serviceFee > 0"> <Text>Service fee: {serviceFee.toFixed(2)} PLN</Text> </BladeIf> <Text>Total: {total.toFixed(2)} PLN</Text> </Html> ); }
Compiled Blade output:
@foreach($items as $item) <p>{{ $item['name'] }} — {{ $item['price'] }} PLN</p> @endforeach @if($serviceFee > 0) <p>Service fee: {{ $serviceFee }} PLN</p> @endif <p>Total: {{ $total }} PLN</p>
blade() helper reference
| Helper | TypeScript type | Use when |
|---|---|---|
blade('name') |
any |
generic placeholder |
blade.string('name') |
string |
plain text, no method calls |
blade.number('name') |
number |
.toFixed(), arithmetic |
blade.array<T>('name') |
T[] |
.map(), .filter(), .reduce() |
blade.object<T>('name') |
T |
nested property access |
Commands
| Command | Description |
|---|---|
php artisan make:react-email <Name> |
Generate Mailable + TSX template |
php artisan react-email:build |
Compile all templates to Blade views |
php artisan react-email:dev |
Start live-preview server at localhost:3000 |
Configuration
config/react-email.php:
return [ // Source TSX templates 'path' => env('REACT_EMAIL_PATH', resource_path('react-email')), // Compiled Blade output 'build_path' => env('REACT_EMAIL_BUILD_PATH', resource_path('views/react-email')), ];
Requirements
- PHP 8.1 – 8.5
- Laravel 10, 11 or 12
- Node.js 16+
License
MIT — see LICENSE.