Email Templating with React Email using Codegen

The following are steps to create email template within Plasmic Studio with React email components using codegen minus WYSIWYG and sending it as email.

The following were referred to concoct this showcase.

[0] Reference
[0] [1] plasmic/examples/react-email at master · plasmicapp/plasmic · GitHub
[0] [2] React Email - React Email

Here the steps and relevant codes

[1] Create Plasmic app via CLI

npx create-plasmic-app { this will use Nextjs v15 as of December 2024 }
? What is your project named? { Name your project }
? What language do you want to use? TypeScript
? What React framework do you want to use? Next.js
? Which scheme do you want to use to integrate Plasmic? Codegen

? If you don’t have a project yet, create one by going to Plasmic.
What is the URL of your project? { Copy and paste your project url }
.
.
.
√ Would you like to use Turbopack for next dev? … No / Yes  { Press Enter to Yes }

[1] [1] Use React 18 if such error occurs:
{
npm warn ERESOLVE overriding peer dependency
npm warn While resolving: @plasmicapp/react-ssr-prepass@2.0.9
npm warn Found: react@19.0.0
npm warn node_modules/react
.
.
.
}

[1] [1] [2] npm uninstall react react-dom @types/react @types/react-dom

[1] [1] [3] npm install react@18 react-dom@18 @types/react@18 @types/react-dom@18

[2] Install dependencies for the react-email

[2] [1] npm install @react-email/components -E

[2] [2] npm install @react-email/render -E

[3] Create React email components

[3] [1] Html  components/EmailHtml.tsx

import React from "react";
import { Html } from "@react-email/components";

interface EmailHtmlProps {
  children?: React.ReactNode;
  lang?: string;
  dir?: string;
  className?: string;
}

const EmailHtml: React.FC<EmailHtmlProps> = ({
  children,
  lang = "en",
  dir = "ltr",
  className,
}) => {

  return (
    <Html lang={lang} dir={dir} className={className}>
      {children}
    </Html>
  );
};

export { EmailHtml };

[3] [2] Button  components/EmailButton.tsx

import React from "react";
import { Button } from "@react-email/components";

interface EmailButtonProps {
  children?: React.ReactNode;
  href?: string;
  pX?: number;
  pY?: number;
  lang?: string;
  dir?: string;
  style?: React.CSSProperties;
}

const EmailButton: React.FC<EmailButtonProps> = ({
  children = "Click me",
  href,
  pX = 16,
  pY = 8,
  lang,
  dir,
  style,
}) => {
  const buttonStyle: React.CSSProperties = {
    backgroundColor: "#EEF8FF",
    borderRadius: "8px",
    color: "#0B5366",
    fontWeight: 600,
    padding: `${pY}px ${pX}px`,
    ...style,
  };

  return (
    <Button href={href} lang={lang} dir={dir} style={buttonStyle}>
      {children}
    </Button>
  );
};

export { EmailButton };

[3] [3] Container  components/EmailContainer.tsx

import React from "react";
import { Container } from "@react-email/components";

interface EmailContainerProps {
  children?: React.ReactNode;
  className?: string;
}

const EmailContainer: React.FC<EmailContainerProps> = ({
  children,
  className,
}) => {
  return (
    <Container className={className}>
      {children}
    </Container>
  );
};

export { EmailContainer };

[3] [4] Img  components/EmailImg.tsx

import React from "react";
import { Img } from "@react-email/components";

interface EmailImgProps {
  src?: string;
  alt?: string;
  width?: number;
  height?: number;
}

const EmailImg: React.FC<EmailImgProps> = ({
  src = "https://picsum.photos/300/200",
  alt = "",
  width,
  height,
}) => {
  return (
    <Img src={src} alt={alt} width={width} height={height} />
  );
};

export { EmailImg };

[3] [5] Section  components/EmailSection.tsx

import React from "react";
import { Section } from "@react-email/components";

interface EmailSectionProps {
  children?: React.ReactNode;
  className?: string;
}

const EmailSection: React.FC<EmailSectionProps> = ({
  children,
  className,
}) => {
  return (
    <Section className={className}>
      {children}
    </Section>
  );
};

export { EmailSection };

[3] [6] Text  components/EmailText.tsx

import React from "react";
import { Text } from "@react-email/components";

interface EmailTextProps {
  style?: React.CSSProperties;
  children?: React.ReactNode;
}

const EmailText: React.FC<EmailTextProps> = ({
  style,
  children = "Hello world!",
}) => {
  return (
    <Text style={style}>
      {children}
    </Text>
  );
};

export { EmailText };

[4] Register all the above components in plasmic-host.tsx

import * as React from 'react';
import { PlasmicCanvasHost, registerComponent } from '@plasmicapp/react-web/lib/host';
import { EmailButton } from '../components/EmailButton';
import { EmailContainer } from '../components/EmailContainer';
import { EmailHtml } from '../components/EmailHtml';
import { EmailImg } from '../components/EmailImg';
import { EmailSection } from '../components/EmailSection';
import { EmailText } from '../components/EmailText';

// You can register any code components that you want to use here; see
// https://docs.plasmic.app/learn/code-components-ref/
// And configure your Plasmic project to use the host url pointing at
// the /plasmic-host page of your nextjs app (for example,
// http://localhost:3000/plasmic-host).  See
// https://docs.plasmic.app/learn/app-hosting/#set-a-plasmic-project-to-use-your-app-host

// registerComponent(...)
registerComponent(EmailHtml, {
  name: "EmailHtml",
  props: {
    children: {
      type: "slot",
    },
    lang: {
      type: "string",
      advanced: true,
    },
    dir: {
      type: "string",
      advanced: true,
    },
  },
  defaultStyles: {
    width: "full-bleed",
  },
  importPath: './components/EmailHtml',
});

registerComponent(EmailSection, {
  name: "EmailSection",
  props: {
    children: {
      type: "slot",
    },
  },
  importPath: './components/EmailSection',
});

registerComponent(EmailContainer, {
  name: "EmailContainer",
  props: {
    children: {
      type: "slot",
    },
  },
  importPath: './components/EmailContainer',
});

registerComponent(EmailText, {
  name: "EmailText",
  props: {
    style: "object",
    children: {
      type: "slot",
      mergeWithParent: true,
      defaultValue: {
        type: "text",
        value: "Hello world!",
      },
    } as any,
  },
  importPath: './components/EmailText',
});

registerComponent(EmailButton, {
  name: "EmailButton",
  props: {
    children: {
      type: "slot",
      mergeWithParent: true,
      defaultValue: {
        type: "text",
        value: "Click me",
        styles: {
          color: "#0B5366",
          fontWeight: "600",
        },
      },
    } as any,
    href: {
      type: "string",
      displayName: "Link",
    },
    pX: {
      displayName: "Padding X",
      type: "number",
      defaultValue: 16,
    },
    pY: {
      displayName: "Padding Y",
      type: "number",
      defaultValue: 8,
    },
    lang: {
      type: "string",
      advanced: true,
    },
    dir: {
      type: "string",
      advanced: true,
    },
  },
  defaultStyles: {
    backgroundColor: "#EEF8FF",
    borderRadius: "8px",
  },
  importPath: './components/EmailButton',
});

registerComponent(EmailImg, {
  name: "EmailImg",
  props: {
    src: { type: "string", defaultValue: "https://picsum.photos/300/200" },
    alt: "string",
    width: "number",
    height: "number",
  },
  importPath: './components/EmailImg',
});

export default function PlasmicHost() {
  return <PlasmicCanvasHost />;
}

[5] Prep your API to render email friendly html  pages/api/render-email.tsx

import type { NextApiRequest, NextApiResponse } from "next";
import * as React from "react";
import { render } from "@react-email/render";

// Manually enter the following for your email template which you can get from the components/plasmic/{Your Project Name}/...
import { PlasmicTestPage } from "../../components/plasmic/react_email_template_v_2/PlasmicTestPage";

// A list of email templates map to imported item as above
const templates: Record<string, React.FC> = {
  TestPage : PlasmicTestPage,
};

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const { template } = req.query;

    if (!template || typeof template !== "string") {
      return res.status(400).json({ error: "Template name is required." });
    }

    const EmailTemplate = templates[template];
    if (!EmailTemplate) {
      return res.status(404).json({ error: `Template "${template}" not found.` });
    }

    const html = await render(  
        <EmailTemplate/>
    );
  
    res.setHeader("Content-Type", "text/html; charset=utf-8");
    res.status(200).send(html);
  } catch (error) {
    console.error("Error rendering email template:", error);
    res.status(500).json({ error: "Failed to render the template." });
  }
}

[5] [1] Attention needs to be paid on the following lines of code for the API.

[5] [1] [1] // Manually enter the following for your email template which you can get from the components/plasmic/{Your Project Name}/…

[5] [1] [1] [1] Example:
import { PlasmicTestPage } from “…/…/components/plasmic/react_email_template_v_2/PlasmicTestPage”;

[5] [1] [1] [2] Manually entered the Page Components that are your email templates from folder components/plasmic/{ Here Should Be Your Project Name }/{ Here is the name of your email template begin with “Plasmic”… } as Import…

[5] [1] [2] // A list of email templates map to imported item as above
const templates: Record<string, React.FC> = {
TestPage : PlasmicTestPage,
};

[5] [1] [2] [1] Enter a name associated with the email template which is the query parameter and assigned it to the imported Page Components.

[5] [1] [2] [1] Example: TestPage : PlasmicTestPage

Hi appreciate input/ advise with regards to perhaps its CSS for the above because it seems the presentation in Plasmic Studio is not the same as in browser.

This the view from Plasmic Studio:

This the view from browser:

This the console Network Tab response for above codes

This is from https://plasmic-react-email.vercel.app/api/email?template=Basic

Based on the comparison I believe the CSS part is missing from the codes I showcased.

Hope for some enlightment.

Thank you.