Set Up React Email Templates for Cloud Projects
In this guide, you'll learn how to set up React Email templates for sending emails in Cloud projects.
What is React Email?#
React Email is a library that allows you to create email templates using React components. You can then transform these templates into HTML strings for sending emails.
React Email also allows you to preview and test your email templates during development.
In this guide, you'll learn how to:
- Install React Email dependencies in your Medusa project.
- Create an invite user email template using React Email.
- Preview email templates during development.
- Send emails using the React Email template with Medusa Emails.
You'll also find examples of common email templates built with React Email at the end of this guide.
email channel configured. You also optionally need to verify your sending domain to send emails to users outside your organization.Step 1: Install React Email Dependencies#
In the Medusa project where you want to set up email templates, install the following dependencies:
These packages include:
@react-email/components: Provides pre-built React components for building email templates.@react-email/render: Renders React Email templates to HTML strings.@react-email/preview-serverandreact-email: Development dependencies for previewing and testing email templates.
Step 2: Create Invite User Email Template#
Next, create the src/emails directory to store your email templates in your Medusa project.
Then, create your email templates as React components in that directory.
For example, to create an email template for sending invites to new admin users, create the file src/emails/invite-user.tsx with the following content:
1import {2 Text,3 Container,4 Heading,5 Html,6 Section,7 Tailwind,8 Head,9 Preview,10 Body,11 Button,12} from "@react-email/components"13 14type InviteEmailProps = {15 inviteUrl: string16 storeName: string17}18 19function Template({ inviteUrl, storeName }: InviteEmailProps) {20 return (21 <Tailwind>22 <Html className="font-sans bg-gray-100">23 <Head />24 <Preview>{`You've been invited to join ${storeName ?? ""}`}</Preview>25 <Body className="bg-white my-10 mx-auto w-full max-w-2xl">26 {/* Main Content */}27 <Container className="p-6 text-center">28 <Heading className="text-lg font-semibold text-black mb-6">29 You've been invited to join {storeName ?? ""}30 </Heading>31 32 <Text className="text-sm text-black leading-relaxed mb-6">33 Click the button below to accept the invitation and get started.34 </Text>35 36 <Section className="text-center my-8">37 <Button38 href={inviteUrl}39 className="bg-black text-white py-3 px-8 inline-block"40 >41 Accept invitation42 </Button>43 </Section>44 </Container>45 46 {/* Footer */}47 <Section className="bg-gray-50 p-6 mt-10">48 <Text className="text-center text-black text-xs mt-4">49 © {new Date().getFullYear()} {storeName}, Inc. All rights reserved.50 </Text>51 </Section>52 </Body>53 </Html>54 </Tailwind>55 )56}57 58export default function getInviteTemplate(props?: InviteEmailProps) {59 return (60 <Template 61 inviteUrl={props?.inviteUrl ?? "#"} 62 storeName={props?.storeName ?? "Demo Store"}63 />64 )65}
In the file, you define:
- A React component
Templatethat uses React Email components to structure the email. - A default export function
getInviteTemplatethat returns theTemplatecomponent with default props.
The file must export a function that returns a React component representing the email template. React Email uses this function to preview the email, and you'll use it later to render the template to HTML.
Step 3: Preview Email Template (Optional)#
It's useful to preview your email templates during development to ensure they look as expected. This allows you to catch any design issues or mistakes early.
To preview your email template, use the React Email Preview Server.
First, add the following script to the scripts section of your package.json:
This script uses the email dev command from react-email, specifying the directory where your email templates are located.
Then, run the preview server with the following command:
The preview server will start at http://localhost:3000, where you can view and test your email templates in the browser.
Step 4: Send Email Using Template#
Once you have the email template, you can use it to send emails from your Medusa project deployed on Cloud.
For example, to send an invite email when a new admin user is invited, create a subscriber with the following content:
1import type {2 SubscriberArgs,3 SubscriberConfig,4} from "@medusajs/framework"5import { render, pretty } from "@react-email/render"6import getInviteTemplate from "../emails/invite-user"7 8export default async function inviteCreatedHandler({9 event: { data },10 container,11}: SubscriberArgs<{ id: string }>) {12 const query = container.resolve("query")13 14 const {15 data: [store],16 } = await query.graph({17 entity: "store",18 fields: ["name"],19 })20 21 const {22 data: [invite],23 } = await query.graph({24 entity: "invite",25 fields: ["email", "token"],26 filters: {27 id: data.id,28 },29 })30 31 const config = container.resolve("configModule")32 33 const adminPath = config.admin.path34 35 const inviteUrl = `/${adminPath}/invite?token=${invite.token}`36 37 const notificationModule = container.resolve("notification")38 39 const html = await pretty(40 await render(getInviteTemplate({41 inviteUrl,42 storeName: store.name,43 }))44 )45 46 await notificationModule.createNotifications({47 to: invite.email,48 // optional: set from email49 // from: "no-reply@yourdomain.com"50 channel: "email",51 content: {52 html,53 subject: `You've been invited to join ${store.name}`,54 },55 })56}57 58export const config: SubscriberConfig = {59 event: [60 "invite.created",61 "invite.resend",62 ],63}
This subscriber listens for the invite.created and invite.resend events. When triggered, it:
- Retrieves the store name and invite details.
- Constructs the invite URL. Invites to the admin dashboard are at the
/admin/invitepath and include the invite token as atokenquery parameter. - Renders the invite email template to an HTML string using React Email's
renderfunction, passing thegetInviteTemplatefunction with the invite URL and store name as props. - Sends the email by creating a notification using Medusa's Notification Module.
Test Email Sending on Cloud#
To test that your emails are sent correctly on Cloud, make sure you've pushed all changes to the project's repository, and the changes have been deployed to your Cloud project.
Then, invite a new admin user to your Cloud project through the Medusa Admin dashboard.
If everything is set up correctly, the new admin user should receive the invite email using your React Email template.
Otherwise, refer to the Troubleshooting Medusa Emails on Cloud guide for help resolving common issues.
Common Email Templates#
This section provides examples of common email templates you can create using React Email, then send in your Cloud projects. You can customize these templates to fit your brand and use case.
You can also refer to the React Email documentation for more information on building email templates with React Email.
Order Placed Email Template#
The Order Placed email template notifies customers when they successfully place an order. It typically includes order details, shipping information, and a thank-you message.
You can use the following template for your Order Placed email:
1import { CustomerDTO, OrderDTO } from "@medusajs/framework/types"2import {3 Body,4 Column,5 Container,6 Head,7 Heading,8 Html,9 Img,10 Preview,11 Row,12 Section,13 Tailwind,14 Text,15} from "@react-email/components"16 17type OrderPlacedEmailProps = {18 order: OrderDTO & { customer: CustomerDTO }19 storeName: string20}21 22function Template({ order, storeName }: OrderPlacedEmailProps) {23 const getLocaleAmount = (amount: number) => {24 const formatter = new Intl.NumberFormat([], {25 style: "currency",26 currencyDisplay: "narrowSymbol",27 currency: order.currency_code,28 })29 30 return formatter.format(amount)31 }32 33 const items = order.items?.map((item) => {34 const itemTotal = item.unit_price * item.quantity35 36 return (37 <Section key={item.id} className="border-b border-gray-200 py-4 px-0">38 <Row className="px-0">
Then, to send the Order Placed email when an order is created, create a subscriber at src/subscribers/order-created.ts with the following content:
1import type {2 SubscriberArgs,3 SubscriberConfig,4} from "@medusajs/framework"5import { render, pretty } from "@react-email/render"6import getOrderPlacedTemplate from "../emails/order-placed"7 8export default async function orderPlacedHandler({9 event: { data },10 container,11}: SubscriberArgs<{ id: string }>) {12 const query = container.resolve("query")13 14 const {15 data: [store],16 } = await query.graph({17 entity: "store",18 fields: ["name"],19 })20 21 const {22 data: [order],23 } = await query.graph({24 entity: "order",25 fields: [26 "id",27 "email",28 "display_id",29 "created_at",30 31 // Customer32 "customer.first_name",33 "customer.email",34 35 // Items36 "items.id",37 "items.product_title",38 "items.variant_title",39 "items.quantity",40 "items.unit_price",41 42 // Shipping address43 "shipping_address.first_name",44 "shipping_address.last_name",45 "shipping_address.address_1",46 "shipping_address.address_2",47 "shipping_address.city",48 "shipping_address.postal_code",49 "shipping_address.country_code",50 51 // Totals52 "item_total",53 "shipping_total",54 "discount_total",55 "tax_total",56 "total",57 ],58 filters: {59 id: data.id,60 },61 })62 63 if (!order.customer?.email) {64 console.warn(`Order ${order.id} has no customer email, skipping notification`)65 return66 }67 68 const notificationModule = container.resolve("notification")69 70 const html = await pretty(71 await render(getOrderPlacedTemplate({72 order: order as any,73 storeName: store.name,74 }))75 )76 77 await notificationModule.createNotifications({78 to: order.customer.email,79 // optional: set from email80 // from: "no-reply@yourdomain.com"81 channel: "email",82 content: {83 html,84 subject: `Order Confirmation - ${store.name}`,85 },86 })87}88 89export const config: SubscriberConfig = {90 event: "order.placed",91}
This subscriber listens for the order.placed event. When triggered, it:
- Retrieves the store name and order details.
- Renders the Order Placed email template to an HTML string using React Email's
renderfunction, passing thegetOrderPlacedTemplatefunction with the order and store name as props. - Sends the email by creating a notification using Medusa's Notification Module.
Whenever a new order is placed, the customer will receive an Order Placed email using your React Email template.
Reset Password Email Template#
The Reset Password email template allows customers and admin users to reset their password if they forget it. It typically includes a link to a password reset page.
You can use the following template for your Reset Password email:
1import {2 Text,3 Container,4 Heading,5 Html,6 Section,7 Tailwind,8 Head,9 Preview,10 Body,11 Button,12} from "@react-email/components"13 14type ResetPasswordEmailProps = {15 resetPasswordUrl: string16 storeName: string17}18 19function Template({ resetPasswordUrl, storeName }: ResetPasswordEmailProps) {20 return (21 <Tailwind>22 <Html className="font-sans bg-gray-100">23 <Head />24 <Preview>Reset your password</Preview>25 <Body className="bg-white my-10 mx-auto w-full max-w-2xl">26 {/* Main Content */}27 <Container className="p-6 text-center">28 <Heading className="text-lg font-semibold text-black mb-6">29 You have submitted a password change request.30 </Heading>31 32 <Section className="text-center my-8">33 <Button34 href={resetPasswordUrl}35 className="bg-black text-white py-3 px-8 inline-block"36 >37 Reset password38 </Button>39 </Section>40 41 <Text className="text-sm text-black leading-relaxed mt-6">42 If you didn't request a password reset, you can safely ignore this43 email.44 </Text>45 </Container>46 47 {/* Footer */}48 <Section className="bg-gray-50 p-6 mt-10">49 <Text className="text-center text-black text-xs mt-4">50 © {new Date().getFullYear()} {storeName}, Inc. All rights reserved.51 </Text>52 </Section>53 </Body>54 </Html>55 </Tailwind>56 )57}58 59export default function getResetPasswordTemplate(props?: ResetPasswordEmailProps) {60 return (61 <Template62 resetPasswordUrl={props?.resetPasswordUrl ?? "#"}63 storeName={props?.storeName ?? "Demo Store"}64 />65 )66}
Then, to send the Reset Password email when a password reset is requested, create a subscriber at src/subscribers/reset-password.ts with the following content:
1import {2 SubscriberArgs,3 type SubscriberConfig,4} from "@medusajs/medusa"5import { Modules } from "@medusajs/framework/utils"6import { render, pretty } from "@react-email/render"7import getResetPasswordTemplate from "../emails/reset-password"8 9export default async function resetPasswordTokenHandler({10 event: { data: {11 entity_id: email,12 token,13 actor_type,14 } },15 container,16}: SubscriberArgs<{ entity_id: string, token: string, actor_type: string }>) {17 const notificationModuleService = container.resolve(18 Modules.NOTIFICATION19 )20 const config = container.resolve("configModule")21 const query = container.resolve("query")22 23 const {24 data: [store],25 } = await query.graph({26 entity: "store",27 fields: ["name"],28 })29 30 let urlPrefix = ""31 32 if (actor_type === "customer") {33 urlPrefix = config.admin.storefrontUrl || "https://storefront.com"34 } else {35 const backendUrl = config.admin.backendUrl !== "/" ? config.admin.backendUrl :36 "http://localhost:9000"37 const adminPath = config.admin.path38 urlPrefix = `${backendUrl}${adminPath}`39 }40 41 const resetPasswordUrl = `${urlPrefix}/reset-password?token=${token}&email=${email}`42 43 const html = await pretty(44 await render(getResetPasswordTemplate({45 resetPasswordUrl,46 storeName: store.name,47 }))48 )49 50 await notificationModuleService.createNotifications({51 to: email,52 // optional: set from email53 // from: "no-reply@yourdomain.com"54 channel: "email",55 content: {56 html,57 subject: `Reset your password - ${store.name}`,58 },59 })60}61 62export const config: SubscriberConfig = {63 event: "auth.password_reset",64}
This subscriber listens for the auth.password_reset event. When triggered, it:
- Retrieves the store name.
- Constructs the reset password URL based on whether the request is for a customer or admin user.
- Renders the Reset Password email template to an HTML string using React Email's
renderfunction, passing thegetResetPasswordTemplatefunction with the reset password URL and store name as props. - Sends the email by creating a notification using Medusa's Notification Module.
Whenever a user requests a password reset, they will receive a Reset Password email using your React Email template.