React Email Builder: Visual Editor, React Components, or a Plain String?
A React email builder is three different products: an embedded editor, a component library, or a plain string. The question that decides which one you need.
"React email builder" names three different products, and the search results mix them freely: a drag-and-drop editor you embed so non-developers can compose email, a code-first component library that compiles React to email-safe HTML, and a plain template string in a server file. Which one you need is decided by one question — who edits the email after launch.
This storefront sends three transactional emails and uses the third option: no email dependency at all, 210 lines across three files. That is not advice for every project. It is advice for email a developer writes once and nobody else touches. This post gives the decision rule, the real files on both sides of it, and the costs each option adds that its own documentation does not mention.
Three things called "a React email builder"
| Visual editor SDK | Code-first components | Template string | |
|---|---|---|---|
| Examples | Unlayer React Email Editor, Beefree SDK | React Email, MJML | text: / html: in a server file |
| Who authors a message | Anyone, in a browser | A developer, in the repo | A developer, in the repo |
| What it produces | HTML plus a saved design (JSON) | Compiled, inlined HTML | Exactly what you typed |
| Ships to your users' browsers | The editor bundle, if you embed it | Nothing | Nothing |
| Preview | Built into the editor | Local dev preview | Send yourself one |
| Review happens in | A visual editor | A pull request | A pull request |
| Earns its keep when | Non-developers change email | Layouts are rich and reused | The email is six lines |
The first column is a product your team logs into. The second is a build step. The third is a string. They are sold as the same category because all three answer "how do I make an email in React", but only the first one changes who is allowed to write copy — which is the expensive part of the decision, not the API.
The question that decides it
Who edits this email three months after launch?
If the answer is a marketer, a client, or anyone who will not open a pull request, you are buying an editor. Judge it on the storage format, not the component API: the editor saves a design as JSON and renders HTML from it, so the JSON is your content and it lives in the vendor's schema. Ask whether you can export it before you build on it.
If the answer is "a developer, in the repo", the editor UI is the one feature you will never use. What is left is the HTML problem — email clients ignore most modern CSS, and Outlook on Windows renders with Word's engine — and that problem only exists once your email has a layout. A single column of sentences has no layout to break.
That splits cleanly along transactional versus campaign email:
| Transactional | Campaign / marketing | |
|---|---|---|
| Distinct messages | Small and fixed | Grows every week |
| Written by | A developer, once | Whoever owns the campaign |
| Layout | One column, one link | Hero image, columns, buttons |
| Changes because | The product changed | The offer changed |
| Fits | A string, or components | An editor |
Most "best React email builder" roundups are published by editor vendors, so they answer the campaign question. If you arrived at that search because an order confirmation needs sending, you are reading the wrong aisle.
What this storefront actually sends
Three emails, three files, no email package:
| File | Message | Lines | Body |
|---|---|---|---|
src/lib/actions/contact.ts | Contact form, to support | 66 | text |
src/lib/email/purchaseWelcome.ts | Purchase confirmation, to the buyer | 72 | text |
src/lib/email/refundRequest.ts | New refund request, to support | 72 | text |
package.json carries 13 runtime dependencies — next, react, react-dom, the two Supabase clients, the MDX pipeline, lucide-react, @vercel/analytics, server-only. None of them is an email package. All three senders POST to the same endpoint with fetch; there is no SDK either.
Here is the whole compose step of the purchase confirmation, copied from src/lib/email/purchaseWelcome.ts:
const dashboardUrl = `${siteUrl()}/dashboard`;
const tierLabel = TIER_LABELS[tier];
const lines = [
`Thanks for your purchase! Your ${tierLabel} plan is ready to download.`,
"",
`Go to your dashboard: ${dashboardUrl}`,
];
if (tier !== "t3") {
lines.push(
"",
"Pick which template(s) you'd like to redeem from your dashboard.",
);
}
const text = lines.join("\n");
And the send, which is the same shape in all three files — the two that mail support add a reply_to so a human can answer:
const res = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "ASoc <noreply@asoctemplates.com>",
to: [email],
subject: "Your ASoc downloads are ready",
text,
}),
});
Three things made the string the right answer here, and all three are properties of the email rather than of the tooling:
- The email's entire job is to carry one link. The buyer needs
/dashboard. A hero image would not make the plan arrive faster. - Plain text cannot render wrong. There is no Outlook bug for a paragraph. The one conditional in the body — the line about redeeming slots, which only non-bundle tiers see — is an
if, not a template variable in a vendor's schema. - It runs on a path that must not throw. The confirmation is sent from the order webhook after the entitlement transaction has committed, so every branch in that file resolves to
voidand logs on failure. A compile step is one more thing that can throw where nothing is allowed to.
The refund notification is the same shape with a different recipient and a reply_to set to the buyer, so support can answer by hitting reply. That is about fifteen lines of string building, and a builder would have added a template to maintain for it.
When a builder earns its keep
Three cases where the answer flips, honestly:
- Someone outside the repo owns the copy. An embedded editor is the only option on this list that lets a marketer ship a change without a deploy. Everything else is theatre.
- You have a dozen messages sharing a layout. This is React Email's case: a
Layout, aButton, aHeroreused across templates, with table markup and inline styles generated for you. Hand-writing that HTML twelve times is how you end up with twelve subtly different headers. - Localisation or heavy personalisation. Components plus a data layer beat strings the moment the same message exists in five languages.
One cost the editor SDKs do not advertise: an embedded editor is a third-party script inside your app, so it has to pass your Content Security Policy. The CSP in this repo's next.config.ts names every external origin explicitly — adding an editor means a script-src entry, usually a frame-src entry for its iframe, and a retest of the page in production rather than in dev, which is where header problems hide. Budget that before you budget the integration.
The part no builder solves
A compose layer decides what the email says. It does not decide what happens when the send fails, and that is where transactional email actually breaks. This repo's own defect was exactly there: a send that was never awaited worked perfectly in development and silently dropped mail in production, because the serverless function froze the moment it returned a response. The fix is in transactional email in Next.js, along with the rule it produced — the contact form reports its failure to the user, the purchase confirmation swallows its own, and the choice is made per email by the caller, never by the template.
The other thing no builder solves is the sending domain. If the email must look like it came from you, the DNS records are yours to publish, whatever composes the body. EmailJS and what ships in your bundle covers the version of this where the send itself moves into the browser, and why that trade is worse than it looks.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Looks right in the editor preview, broken in Outlook | The preview renders in a browser engine; Outlook on Windows uses Word's | Test in the real client; keep the library's table markup instead of hand-editing it |
| The editor inflates an unrelated route's bundle | Imported at module scope in a shared client component | Load it only in the route that needs it |
Email arrives showing raw <p> tags | HTML passed in the text field | Send html, and keep text as the plain-text alternative |
| Dev sends fine, production sends nothing | The send was not awaited; the function froze after the response | Await it, or hand it to the platform's after-response API |
| A mail failure rolls back the purchase | The send throws inside the transaction path | Catch and log in the sender; never let it reach the caller |
| Design unrecoverable after a vendor switch | The design JSON was never exported | Store the exported design alongside the message that uses it |
| Styles vanish in one client only | A class-based stylesheet, not inlined | Use a library that inlines styles at compile time |
Frequently asked questions
Is React Email a builder? Not in the drag-and-drop sense. It is a component library plus a local preview: you write React, it compiles to email-safe HTML with inline styles. Nobody outside your repo can author a message with it, which is the whole distinction this post turns on.
Can I use a visual email builder with the Next.js App Router? Yes, with the usual split — the editor is a client component (it is a DOM application), while the compile or save step belongs in a Server Action or route handler so no API key reaches the browser. The same boundary rule as any third-party editor.
Do I need a builder for one order confirmation? No. One confirmation is a string, as the 72-line file above shows. Reach for a builder on the second or third message that shares a layout, not before.
Can I reuse my Tailwind classes in email?
Not directly — many clients drop class-based CSS, so a library has to inline the result at build time. The design tokens in this repo's src/app/globals.css are never used in email for that reason; the storefront's visual system stops at the browser, and the email is plain text on purpose.
Start from a finished front end
Email is the smallest surface in a product and the one most likely to be over-tooled. The page that earns the order is the one worth spending design time on, and that part you can inherit finished.
ASoc Fade is a barbershop website template, ASoc Fiscal a financial platform landing page, and ASoc Flow a workflow automation landing page — each a typed, statically rendered Next.js project you own outright.
Browse the full set of Next.js landing page templates, or the Tailwind landing page templates if you would rather bring your own framework.
