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.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CONTRIBUTING.md +68 -0
  4. data/LICENSE.md +21 -0
  5. data/README.md +492 -0
  6. data/SECURITY.md +5 -0
  7. data/lib/generators/react_email_rails/USAGE +19 -0
  8. data/lib/generators/react_email_rails/email_generator.rb +224 -0
  9. data/lib/generators/react_email_rails/install_generator.rb +211 -0
  10. data/lib/generators/react_email_rails/templates/USAGE +33 -0
  11. data/lib/generators/react_email_rails/templates/email/application_mailer.rb.tt +5 -0
  12. data/lib/generators/react_email_rails/templates/email/component.tsx +14 -0
  13. data/lib/generators/react_email_rails/templates/email/mailer.rb.tt +17 -0
  14. data/lib/generators/react_email_rails/templates/email/mailer_preview.rb.tt +14 -0
  15. data/lib/generators/react_email_rails/templates/email/mailer_test.rb.tt +28 -0
  16. data/lib/generators/react_email_rails/templates/initializer.rb +3 -0
  17. data/lib/generators/react_email_rails/templates/vite.config.ts +6 -0
  18. data/lib/react-email-rails.rb +1 -0
  19. data/lib/react_email_rails/action_mailer.rb +31 -0
  20. data/lib/react_email_rails/configuration.rb +145 -0
  21. data/lib/react_email_rails/props_resolver.rb +48 -0
  22. data/lib/react_email_rails/railtie.rb +16 -0
  23. data/lib/react_email_rails/render_error.rb +1 -0
  24. data/lib/react_email_rails/render_modes/persistent/command_runner.rb +44 -0
  25. data/lib/react_email_rails/render_modes/persistent/server.rb +204 -0
  26. data/lib/react_email_rails/render_modes/persistent.rb +28 -0
  27. data/lib/react_email_rails/render_modes/subprocess/command_runner.rb +56 -0
  28. data/lib/react_email_rails/render_modes/subprocess.rb +99 -0
  29. data/lib/react_email_rails/render_modes.rb +1 -0
  30. data/lib/react_email_rails/render_protocol.rb +21 -0
  31. data/lib/react_email_rails/rendered_email.rb +3 -0
  32. data/lib/react_email_rails/version.rb +3 -0
  33. data/lib/react_email_rails.rb +58 -0
  34. 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
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial public release.
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,5 @@
1
+ # Security Policy
2
+
3
+ Please report security issues privately by emailing hi@supertape.com.
4
+
5
+ Do not open a public GitHub issue for suspected vulnerabilities. Include the affected version, a minimal reproduction when possible, and any relevant deployment details.
@@ -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.*.