react-email-rails 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/CONTRIBUTING.md +68 -0
- data/LICENSE.md +21 -0
- data/README.md +492 -0
- data/SECURITY.md +5 -0
- data/lib/generators/react_email_rails/USAGE +19 -0
- data/lib/generators/react_email_rails/email_generator.rb +224 -0
- data/lib/generators/react_email_rails/install_generator.rb +211 -0
- data/lib/generators/react_email_rails/templates/USAGE +33 -0
- data/lib/generators/react_email_rails/templates/email/application_mailer.rb.tt +5 -0
- data/lib/generators/react_email_rails/templates/email/component.tsx +14 -0
- data/lib/generators/react_email_rails/templates/email/mailer.rb.tt +17 -0
- data/lib/generators/react_email_rails/templates/email/mailer_preview.rb.tt +14 -0
- data/lib/generators/react_email_rails/templates/email/mailer_test.rb.tt +28 -0
- data/lib/generators/react_email_rails/templates/initializer.rb +3 -0
- data/lib/generators/react_email_rails/templates/vite.config.ts +6 -0
- data/lib/react-email-rails.rb +1 -0
- data/lib/react_email_rails/action_mailer.rb +31 -0
- data/lib/react_email_rails/configuration.rb +145 -0
- data/lib/react_email_rails/props_resolver.rb +48 -0
- data/lib/react_email_rails/railtie.rb +16 -0
- data/lib/react_email_rails/render_error.rb +1 -0
- data/lib/react_email_rails/render_modes/persistent/command_runner.rb +44 -0
- data/lib/react_email_rails/render_modes/persistent/server.rb +204 -0
- data/lib/react_email_rails/render_modes/persistent.rb +28 -0
- data/lib/react_email_rails/render_modes/subprocess/command_runner.rb +56 -0
- data/lib/react_email_rails/render_modes/subprocess.rb +99 -0
- data/lib/react_email_rails/render_modes.rb +1 -0
- data/lib/react_email_rails/render_protocol.rb +21 -0
- data/lib/react_email_rails/rendered_email.rb +3 -0
- data/lib/react_email_rails/version.rb +3 -0
- data/lib/react_email_rails.rb +58 -0
- metadata +194 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a5aaa4184f5aef5765da5cefbb47b85c6b1619f61d8cf3d7f722c6268fe8bfdf
|
|
4
|
+
data.tar.gz: 25d7d5100887b3976a0c258aa83df20c74080e042bea88f16767c4d7dc227055
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 78c3a0fb601dd5d81d2705872d3d2b7de0ed7876dd02013fc9b9108f0adbabd36e39b1f9dd92128d2a9005ee514679e7fcc3b8b5983ef21374c7d5a57c913349
|
|
7
|
+
data.tar.gz: 7a3a777b82c24144e8447c086004f61cb3c1f9d6dff596c6a309c1d103a6751eef8b78f45445794a028c9e15aea56a2930df11d933cc551d6d95f4cfda4f566e
|
data/CHANGELOG.md
ADDED
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Bug reports and pull requests are welcome.
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
Install dependencies:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
bundle install
|
|
11
|
+
cd vite && pnpm install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Run the core checks before opening a pull request:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
ruby scripts/check_version_sync.rb
|
|
18
|
+
bin/test
|
|
19
|
+
bin/lint
|
|
20
|
+
cd vite && pnpm run build
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The Ruby gem version in `lib/react_email_rails/version.rb` is the package version source of truth. The renderer protocol version in `lib/react_email_rails/render_protocol.rb` is also synced into the Vite package. Run `cd vite && pnpm run sync:version` after changing either one.
|
|
24
|
+
|
|
25
|
+
## Release Checks
|
|
26
|
+
|
|
27
|
+
Before publishing, verify both packages can be built:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
bundle exec rake build
|
|
31
|
+
cd vite && pnpm pack --dry-run
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Publishing
|
|
35
|
+
|
|
36
|
+
Releases are tag-driven. Pushing `vX.Y.Z` to GitHub runs `.github/workflows/release.yml`, publishes the Ruby gem to RubyGems, publishes the Vite package to npm, and creates the GitHub Release with the built `.gem` and `.tgz` artifacts.
|
|
37
|
+
|
|
38
|
+
### Patch, Minor, and Major Releases
|
|
39
|
+
|
|
40
|
+
Prepare the version bump:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
ruby scripts/prepare_release.rb patch
|
|
44
|
+
# or: ruby scripts/prepare_release.rb minor
|
|
45
|
+
# or: ruby scripts/prepare_release.rb major
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Replace the generated `CHANGELOG.md` TODO entry with the actual release notes, then run the checks:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
ruby scripts/check_version_sync.rb
|
|
52
|
+
bin/test
|
|
53
|
+
bin/lint
|
|
54
|
+
cd vite && pnpm run ci && pnpm pack --dry-run
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Commit the release prep on `main` or open and merge a release pull request. Then tag the release commit:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
git switch main
|
|
61
|
+
git pull --ff-only origin main
|
|
62
|
+
VERSION=$(ruby -r ./lib/react_email_rails/version -e 'print ReactEmailRails::VERSION')
|
|
63
|
+
ruby scripts/check_release_tag.rb "v$VERSION"
|
|
64
|
+
git tag -a "v$VERSION" -m "v$VERSION"
|
|
65
|
+
git push origin "v$VERSION"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The GitHub release workflow handles the rest. If publishing fails before both registries are updated, do not reuse the same version unless neither registry accepted it; bump to the next patch version and release again.
|
data/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Supertape
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
# React Email + Rails
|
|
2
|
+
|
|
3
|
+
Build and send emails using React and Rails — a seamless integration between [React Email](https://react.email) and [Action Mailer](https://guides.rubyonrails.org/action_mailer_basics.html).
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Why](#why)
|
|
8
|
+
- [How](#how)
|
|
9
|
+
- [Status](#status)
|
|
10
|
+
- [Requirements](#requirements)
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [Usage](#usage)
|
|
13
|
+
- [Configuration](#configuration)
|
|
14
|
+
- [Deployment](#deployment)
|
|
15
|
+
- [Development](#development)
|
|
16
|
+
- [Contributing](#contributing)
|
|
17
|
+
- [Security](#security)
|
|
18
|
+
- [License](#license)
|
|
19
|
+
|
|
20
|
+
## Why
|
|
21
|
+
|
|
22
|
+
Building HTML emails is painfully archaic. [React Email](https://react.email) is a collection of unstyled components for building emails with React, Tailwind, and TypeScript. This gem brings that power directly into your Rails app: write emails as React components and send them through Action Mailer.
|
|
23
|
+
|
|
24
|
+
## How
|
|
25
|
+
|
|
26
|
+
**In development,** the gem renders components live through Vite's dev pipeline, so your emails get the same module resolution and transforms as the rest of your frontend.
|
|
27
|
+
|
|
28
|
+
**In production,** Vite builds a server-side email bundle ahead of time. The plugin adds a dedicated `email` build environment, so your normal `vite build` emits the bundle alongside your client assets.
|
|
29
|
+
|
|
30
|
+
Delivery, headers, multipart parts, previews, queues, and callbacks all stay normal Action Mailer. If rendering fails, no email is sent and `ReactEmailRails::RenderError` is raised.
|
|
31
|
+
|
|
32
|
+
## Status
|
|
33
|
+
|
|
34
|
+
**react-email-rails is pre-1.0.** It's tested in CI across the supported Ruby, Rails, Node, and Vite versions, but it hasn't been battle-tested in high-volume production environments yet, and the API may still change before 1.0. Please [share feedback and report issues](https://github.com/heysupertape/react-email-rails/issues).
|
|
35
|
+
|
|
36
|
+
## Requirements
|
|
37
|
+
|
|
38
|
+
- Ruby >= 3.3
|
|
39
|
+
- Action Mailer, Active Support, and Railties >= 7.1 and < 9.0
|
|
40
|
+
- Node >= 20.19
|
|
41
|
+
- Vite 7 or 8
|
|
42
|
+
- React 18 or 19
|
|
43
|
+
- `@react-email/render` 2.x
|
|
44
|
+
|
|
45
|
+
> We recommend [rails_vite](https://github.com/skryukov/rails_vite/) for Vite with Rails.
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
Add the gem:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
# Gemfile
|
|
53
|
+
|
|
54
|
+
gem "react-email-rails"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Automatic Install
|
|
58
|
+
|
|
59
|
+
Run the installer:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
bin/rails generate react_email_rails:install
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This creates `config/initializers/react_email_rails.rb`, installs missing JavaScript dependencies when it can detect your package manager, adds `reactEmailRails()` to `vite.config.*`, and creates `app/javascript/emails`.
|
|
66
|
+
|
|
67
|
+
### Manual Install
|
|
68
|
+
|
|
69
|
+
Install the npm package and peer dependencies manually:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
npm i react-email-rails @react-email/render @react-email/components react react-dom
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Update your Vite config to add the plugin:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// vite.config.ts
|
|
79
|
+
|
|
80
|
+
import { defineConfig } from "vite"
|
|
81
|
+
import { reactEmailRails } from "react-email-rails"
|
|
82
|
+
|
|
83
|
+
export default defineConfig({
|
|
84
|
+
plugins: [reactEmailRails()],
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Your First Email
|
|
89
|
+
|
|
90
|
+
Generate a mailer and React Email component:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
bin/rails generate react_email_rails:email Account welcome
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The generator follows Rails' mailer generator shape: `NAME [method method]`. It creates `app/mailers/account_mailer.rb`, matching components under your configured React Email directory, plus a mailer preview and test.
|
|
97
|
+
|
|
98
|
+
The generator reads `emails.path` and `emails.extension` from `reactEmailRails()` when available. Pass flags to override them:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
bin/rails generate react_email_rails:email Account welcome --emails-path=app/emails --extension=jsx
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Edit the generated mailer to pass any necessary props:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
class AccountMailer < ApplicationMailer
|
|
108
|
+
def welcome
|
|
109
|
+
account = params.fetch(:account)
|
|
110
|
+
|
|
111
|
+
mail(
|
|
112
|
+
to: account.email,
|
|
113
|
+
subject: "Welcome",
|
|
114
|
+
react: {
|
|
115
|
+
account: {
|
|
116
|
+
name: account.name,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Edit the generated email component:
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
// app/javascript/emails/account_mailer/welcome.tsx
|
|
128
|
+
|
|
129
|
+
import { Body, Container, Html, Text } from "@react-email/components"
|
|
130
|
+
|
|
131
|
+
type WelcomeProps = {
|
|
132
|
+
account: {
|
|
133
|
+
name: string
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default function Welcome({ account }: WelcomeProps) {
|
|
138
|
+
return (
|
|
139
|
+
<Html>
|
|
140
|
+
<Body>
|
|
141
|
+
<Container>
|
|
142
|
+
<Text>Welcome, {account.name}</Text>
|
|
143
|
+
</Container>
|
|
144
|
+
</Body>
|
|
145
|
+
</Html>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
> [@react-email/components](https://react.email/docs/components/html) provides primitives like `<Button>`, `<Heading>`, `<Tailwind>`, and more.
|
|
151
|
+
|
|
152
|
+
That's it — it now renders and delivers like any other Action Mailer email.
|
|
153
|
+
|
|
154
|
+
## Usage
|
|
155
|
+
|
|
156
|
+
Pass data from your mailers and each top-level key becomes a prop on the React component's default export.
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
mail react: { foo: "bar" }, ...
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
export default function Email({ foo }: { foo: string }) {
|
|
164
|
+
// ...
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Choose the level of inference you want:
|
|
169
|
+
|
|
170
|
+
### Implicit Component, Instance Props
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
class AccountMailer < ApplicationMailer
|
|
174
|
+
use_react_instance_props
|
|
175
|
+
|
|
176
|
+
def welcome
|
|
177
|
+
@account = params.fetch(:account)
|
|
178
|
+
mail react: true, to: @account.email, subject: "Welcome"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Action Mailer's framework assigns (including `params` and `rendered_format`) are excluded from instance props.
|
|
184
|
+
|
|
185
|
+
Without `use_react_instance_props`, `react: true` still infers the component and renders it with no props, which is handy for emails that take no props at all.
|
|
186
|
+
|
|
187
|
+
### Implicit Component, Explicit Props
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
mail(
|
|
191
|
+
...
|
|
192
|
+
react: {
|
|
193
|
+
account: {
|
|
194
|
+
name: account.name,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Explicit Component, Explicit Props
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
mail(
|
|
204
|
+
...
|
|
205
|
+
react: "accounts/welcome",
|
|
206
|
+
props: {
|
|
207
|
+
account: {
|
|
208
|
+
name: account.name,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
These forms mirror [inertia-rails](https://inertia-rails.dev), making the two libraries feel consistent when used together.
|
|
215
|
+
|
|
216
|
+
### Component Names
|
|
217
|
+
|
|
218
|
+
Component names are inferred from the mailer and action:
|
|
219
|
+
|
|
220
|
+
| Mailer action | Component |
|
|
221
|
+
|---------------|-----------|
|
|
222
|
+
| `AccountMailer#welcome` | `account_mailer/welcome` |
|
|
223
|
+
| `Users::InviteMailer#new_invite` | `users/invite_mailer/new_invite` |
|
|
224
|
+
|
|
225
|
+
Rails derives `account_mailer` from `AccountMailer` via its `mailer_name`. The default Vite plugin resolves those names under `app/javascript/emails`, so `account_mailer/welcome` maps to `app/javascript/emails/account_mailer/welcome.tsx` or `.jsx`.
|
|
226
|
+
|
|
227
|
+
Files and directories starting with `_` are ignored as renderable email entries by default. Use them for shared components such as `_components/email_layout.tsx`; they can still be imported by email components.
|
|
228
|
+
|
|
229
|
+
Override the inferred name per mail:
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
mail react: "users/welcome", props: { user: }, to:, subject:
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Or override `component_path_resolver` globally in your [configuration](#configuration).
|
|
236
|
+
|
|
237
|
+
### Prop Serialization
|
|
238
|
+
|
|
239
|
+
Just like `render json:` in controllers, you can pass any object that responds to `as_json` to `mail react:`. Plain hashes, Active Model objects, and serialization libraries like [Alba](https://github.com/okuramasafumi/alba) or [ActiveModel::Serializer](https://github.com/rails-api/active_model_serializers) are supported.
|
|
240
|
+
|
|
241
|
+
### Prop Transformation
|
|
242
|
+
|
|
243
|
+
By default, prop keys are camelized on the way to React, so `account.plan_name` arrives as `account.planName` in your component. This makes them more idiomatic for the frontend, but you can override the `transform_props` behavior in your [configuration](#configuration).
|
|
244
|
+
|
|
245
|
+
### Layouts
|
|
246
|
+
|
|
247
|
+
Action Mailer layouts are not applied to `react:` emails. React Email treats layouts like any other component, so share structure with normal React composition instead:
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
// app/javascript/emails/_components/email_layout.tsx
|
|
251
|
+
|
|
252
|
+
import { Body, Container, Html } from "@react-email/components"
|
|
253
|
+
import type { ReactNode } from "react"
|
|
254
|
+
|
|
255
|
+
type EmailLayoutProps = {
|
|
256
|
+
children: ReactNode
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function EmailLayout({ children }: EmailLayoutProps) {
|
|
260
|
+
return (
|
|
261
|
+
<Html>
|
|
262
|
+
<Body>
|
|
263
|
+
<Container>{children}</Container>
|
|
264
|
+
</Body>
|
|
265
|
+
</Html>
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
// app/javascript/emails/account_mailer/welcome.tsx
|
|
272
|
+
|
|
273
|
+
import { Text } from "@react-email/components"
|
|
274
|
+
import { EmailLayout } from "../_components/email_layout"
|
|
275
|
+
|
|
276
|
+
export default function Welcome() {
|
|
277
|
+
return (
|
|
278
|
+
<EmailLayout>
|
|
279
|
+
<Text>Welcome</Text>
|
|
280
|
+
</EmailLayout>
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
See [Component Names](#component-names) for how shared `_` files are handled.
|
|
286
|
+
|
|
287
|
+
## Configuration
|
|
288
|
+
|
|
289
|
+
Configuration is handled primarily on the Rails side, though there are some Vite options to be aware of.
|
|
290
|
+
|
|
291
|
+
### Rails Configuration
|
|
292
|
+
|
|
293
|
+
If the defaults don't fit, override them in `config/initializers/react_email_rails.rb`:
|
|
294
|
+
|
|
295
|
+
| Option | Default |
|
|
296
|
+
|--------|---------|
|
|
297
|
+
| `component_path_resolver` | `->(mailer:, action:) { "#{mailer}/#{action}" }` |
|
|
298
|
+
| `transform_props` | `:lower_camel` |
|
|
299
|
+
| `render_mode` | `:subprocess` |
|
|
300
|
+
| `render_options` | `{}` |
|
|
301
|
+
| `render_timeout` | `10` seconds |
|
|
302
|
+
| `render_process_max_requests` | `1_000` |
|
|
303
|
+
| `on_render_error` | `nil` |
|
|
304
|
+
| `verify_render_on_boot` | `false` |
|
|
305
|
+
|
|
306
|
+
#### Prop Transformation
|
|
307
|
+
|
|
308
|
+
Set `transform_props` to another supported value if you prefer a different prop key style:
|
|
309
|
+
|
|
310
|
+
| Value | Example |
|
|
311
|
+
|-------|---------|
|
|
312
|
+
| `:camel` | `AccountName` |
|
|
313
|
+
| `:lower_camel` (default) | `accountName` |
|
|
314
|
+
| `:dash` | `account-name` |
|
|
315
|
+
| `:snake` | `account_name` |
|
|
316
|
+
| `:none` | preserves serialized keys |
|
|
317
|
+
|
|
318
|
+
```ruby
|
|
319
|
+
ReactEmailRails.configure do |config|
|
|
320
|
+
config.transform_props = :none
|
|
321
|
+
end
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`transform_props` only controls prop key names; props are always serialized with `as_json`.
|
|
325
|
+
|
|
326
|
+
#### Render Modes
|
|
327
|
+
|
|
328
|
+
`:subprocess` starts a fresh Node process for each render. It's simple, always uses the latest bundle, and keeps failures isolated, but pays Node startup and bundle load on every render.
|
|
329
|
+
|
|
330
|
+
`:persistent` reuses one long-lived Node process per worker. It's faster because it avoids per-render startup, but uses more memory and can serve a stale component until recycled.
|
|
331
|
+
|
|
332
|
+
For background email delivery, the default `:subprocess` mode is usually enough. Switch to `:persistent` when Node startup appears in traces or batch jobs render many emails from the same bundle (see [Instrumentation](#instrumentation)).
|
|
333
|
+
|
|
334
|
+
The render mode also shapes the development experience: `:subprocess` boots a fresh Vite dev server per render and always reflects your latest edits, while `:persistent` reuses the server and may serve a stale component until the process is recycled.
|
|
335
|
+
|
|
336
|
+
Enable persistent mode for render-heavy worker processes:
|
|
337
|
+
|
|
338
|
+
```ruby
|
|
339
|
+
ReactEmailRails.configure do |config|
|
|
340
|
+
config.render_mode = :persistent
|
|
341
|
+
end
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Persistent mode keeps one Node child per process:
|
|
345
|
+
|
|
346
|
+
- Renders are sent as newline-delimited JSON and processed one at a time, so a single child never renders concurrently. Scale throughput by adding worker processes.
|
|
347
|
+
- It is fork-safe: under clustered Puma or forking job runners, each worker spawns its own child.
|
|
348
|
+
- The child is recycled after `render_process_max_requests` renders to bound memory growth. Set it to `nil` to disable recycling.
|
|
349
|
+
|
|
350
|
+
#### Render Options
|
|
351
|
+
|
|
352
|
+
`render_options` is passed to [@react-email/render](https://react.email/docs/utilities/render). `html` options apply to HTML rendering and `text` options apply to plain-text rendering. Keys are camelized before they cross into JavaScript.
|
|
353
|
+
|
|
354
|
+
```ruby
|
|
355
|
+
ReactEmailRails.configure do |config|
|
|
356
|
+
config.render_options = {
|
|
357
|
+
html: {
|
|
358
|
+
pretty: Rails.env.development?
|
|
359
|
+
},
|
|
360
|
+
text: {
|
|
361
|
+
html_to_text_options: {
|
|
362
|
+
selectors: [{ selector: "img", format: "skip" }],
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
}
|
|
366
|
+
end
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
#### Error Reporting
|
|
370
|
+
|
|
371
|
+
Use `on_render_error` to report failures before the exception is re-raised:
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
ReactEmailRails.configure do |config|
|
|
375
|
+
config.on_render_error = ->(error, component:) {
|
|
376
|
+
Rails.error.report(error, context: { component: })
|
|
377
|
+
}
|
|
378
|
+
end
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
#### Instrumentation
|
|
382
|
+
|
|
383
|
+
Every render emits an [ActiveSupport::Notifications](https://guides.rubyonrails.org/active_support_instrumentation.html) event named `render.react-email-rails`, so you can log render timing or forward it to your APM. The payload carries the `component` name and, on success, the rendered HTML size in `html_bytes`:
|
|
384
|
+
|
|
385
|
+
```ruby
|
|
386
|
+
ActiveSupport::Notifications.subscribe("render.react-email-rails") do |event|
|
|
387
|
+
Rails.logger.info(
|
|
388
|
+
"[react-email-rails] rendered #{event.payload[:component]} " \
|
|
389
|
+
"(#{event.payload[:html_bytes]} bytes) in #{event.duration.round(1)}ms"
|
|
390
|
+
)
|
|
391
|
+
end
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Vite Configuration
|
|
395
|
+
|
|
396
|
+
Most apps only need the `reactEmailRails()` plugin from [Quick Start](#quick-start). The options below change where components are discovered and how the bundle handles dependencies.
|
|
397
|
+
|
|
398
|
+
In development, the renderer loads the `reactEmailRails()` plugin, JSX support, and your `resolve`, `define`, and `css` config — but none of your other dev-server plugins.
|
|
399
|
+
|
|
400
|
+
#### Plugin Options
|
|
401
|
+
|
|
402
|
+
| Option | Default | Description |
|
|
403
|
+
|--------|---------|-------------|
|
|
404
|
+
| `emails.path` | `"app/javascript/emails"` | Directory containing email components |
|
|
405
|
+
| `emails.extension` | `[".tsx", ".jsx"]` | Component extension, or an array of extensions |
|
|
406
|
+
| `emails.ignore` | `["**/_*", "**/_*/**"]` | Glob patterns ignored under `emails.path` |
|
|
407
|
+
| `standalone` | `true` | Inline SSR dependencies with `ssr.noExternal: true` |
|
|
408
|
+
|
|
409
|
+
Use a custom directory:
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
reactEmailRails({
|
|
413
|
+
emails: "app/emails",
|
|
414
|
+
})
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Use multiple extensions:
|
|
418
|
+
|
|
419
|
+
```ts
|
|
420
|
+
reactEmailRails({
|
|
421
|
+
emails: {
|
|
422
|
+
extension: [".email.tsx", ".email.jsx"],
|
|
423
|
+
},
|
|
424
|
+
})
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Component names come from the Vite directory layout (see [Component Names](#component-names)). To map mailer actions to a different layout, override `component_path_resolver` on the Ruby side rather than renaming in the plugin, so both halves stay in sync.
|
|
428
|
+
|
|
429
|
+
#### Standalone Builds
|
|
430
|
+
|
|
431
|
+
By default the email bundle inlines React, `@react-email/render`, and other Node dependencies. That makes the bundle larger, but it works well for Rails deploys that build assets in one stage and run without `node_modules` in the final runtime image.
|
|
432
|
+
|
|
433
|
+
Set `standalone: false` when your runtime already ships `node_modules` and you prefer a smaller SSR-style bundle:
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
reactEmailRails({
|
|
437
|
+
standalone: false,
|
|
438
|
+
})
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Externalized bundles are smaller and may build faster, but the renderer needs the externalized packages available at runtime.
|
|
442
|
+
|
|
443
|
+
## Deployment
|
|
444
|
+
|
|
445
|
+
For a standard Rails + Vite deploy, there is nothing extra to configure. Keep running your normal asset build and the email bundle is emitted alongside your client assets.
|
|
446
|
+
|
|
447
|
+
### Standard Vite Builds
|
|
448
|
+
|
|
449
|
+
The plugin registers a dedicated `email` [build environment](https://vite.dev/guide/api-environment), so a normal `vite build` writes `tmp/react-email-rails/emails.js`, which the bundled production renderer runs with Node. With [rails_vite](https://github.com/skryukov/rails_vite/), this already happens during `assets:precompile`.
|
|
450
|
+
|
|
451
|
+
The bundle is required, not an optimization. If it's missing, renders raise `ReactEmailRails::RenderError` and no mail is sent. Make sure `vite build` runs anywhere that renders mail, the same as for the rest of your assets.
|
|
452
|
+
|
|
453
|
+
The Ruby gem and npm package must stay on the same version. The renderer includes a small protocol/version handshake, so mismatched installs fail with an actionable `ReactEmailRails::RenderError` instead of silently returning malformed output.
|
|
454
|
+
|
|
455
|
+
### Custom Vite Builds
|
|
456
|
+
|
|
457
|
+
To emit the bundle without a dedicated command, the plugin opts your project into Vite's [whole-app build](https://vite.dev/guide/api-environment):
|
|
458
|
+
|
|
459
|
+
- A plain `vite build` builds every configured environment in one pass. For a standard client-only app, that's just your client assets plus the `email` bundle.
|
|
460
|
+
- If you've defined other Vite environments, such as a custom `ssr` build, they build in the same pass too.
|
|
461
|
+
- If your Vite config defines a custom `builder.buildApp`, make sure it builds `builder.environments.email` alongside your other environments. Custom builders replace Vite's default whole-app build orchestration.
|
|
462
|
+
|
|
463
|
+
### Runtime Dependencies
|
|
464
|
+
|
|
465
|
+
For runtime dependency tradeoffs, see [Standalone Builds](#standalone-builds).
|
|
466
|
+
|
|
467
|
+
### Boot Verification
|
|
468
|
+
|
|
469
|
+
Boot verification is disabled by default. If you want the app to check the renderer during boot, scope it to the same processes that build or ship the bundle:
|
|
470
|
+
|
|
471
|
+
```ruby
|
|
472
|
+
ReactEmailRails.configure do |config|
|
|
473
|
+
config.verify_render_on_boot = -> { Rails.env.production? && Sidekiq.server? }
|
|
474
|
+
config.render_mode = :persistent if Rails.env.production? && Sidekiq.server?
|
|
475
|
+
end
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
## Development
|
|
479
|
+
|
|
480
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for local setup, checks, formatting, and release verification.
|
|
481
|
+
|
|
482
|
+
## Contributing
|
|
483
|
+
|
|
484
|
+
Bug reports and pull requests are welcome on GitHub. See [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request.
|
|
485
|
+
|
|
486
|
+
## Security
|
|
487
|
+
|
|
488
|
+
Please report vulnerabilities privately. See [SECURITY.md](SECURITY.md) for details.
|
|
489
|
+
|
|
490
|
+
## License
|
|
491
|
+
|
|
492
|
+
The gem and npm package are available as open source under the terms of the [MIT License](LICENSE.md).
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Install React Email Rails into a Rails + Vite application.
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
bin/rails generate react_email_rails:install
|
|
6
|
+
|
|
7
|
+
This creates config/initializers/react_email_rails.rb, installs missing
|
|
8
|
+
JavaScript dependencies when a package manager can be detected, adds
|
|
9
|
+
reactEmailRails() to vite.config.*, and creates app/javascript/emails.
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
--package-manager=npm|pnpm|yarn|bun
|
|
13
|
+
Choose the JavaScript package manager used to install dependencies.
|
|
14
|
+
|
|
15
|
+
--skip-package-install
|
|
16
|
+
Do not install JavaScript dependencies.
|
|
17
|
+
|
|
18
|
+
--skip-vite
|
|
19
|
+
Do not create or update vite.config.*.
|