Creating type safe emails with mjml-react and Typescript
Installing the depedencies
Luckily the folks at Wix created the mjml-react library. Lets start by installing the library and the necessary types by running the following command:
npm install mjml-react @types/mjml-react
Creating a default layout
Next, we have to create our e-mail template. Because we are now using react to create our template we can componentize our email templates very easily. The first thing I do is to create a base layout we can use in all our templates. The example below renders the MjmlHead component in which we can define all our default styles and attributes following the MJML documentation
interface Props {
children: ReactNode;
}
export function Layout(props: Props) {
return (
<Mjml>
<MjmlHead>{/* Add any default head attributes here */}</MjmlHead>
<MjmlBody>{props.children}</MjmlBody>
</Mjml>
);
}
Creating the email template
With the default layout ready and all our base styles defined, we can start building the template. The props you are defining here are the props we are going to infer from the component when we are sending our email.
interface Props {
name: string;
url: string;
}
export function HelloWorld(props: Props) {
return (
<Layout>
<MjmlSection>
<MjmlColumn>
<MjmlText>Hello {props.name}!</MjmlText>
<MjmlButton href={props.url}>Go to page!</MjmlButton>
</MjmlColumn>
</MjmlSection>
</Layout>
);
}
Rendering and sending the email
To render our email we need to create a small helper function that takes in the component and the needed props. By inferring the type of the props from the component we have created full type safety without the need to manually define any types.
import { render } from "mjml-react";
import { ComponentProps, createElement, JSXElementConstructor } from "react";
export async function renderEmail<T extends JSXElementConstructor<any>>(
component: T,
props: ComponentProps<T>
) {
const email = createElement(component, props);
const { html, errors } = render(email, {
minify: false,
});
if (errors.length) {
throw new Error(errors[0].formattedMessage);
}
return html;
}
With the helper function created we can now call it from anywhere and pass the resulting html into a module like Nodemailer
// This passes the type check because our props match
const html = await renderEmail(HelloWorld, {
name: "User",
url: "https://google.com",
});
// This fails the type check because we are missing a prop
const html = await renderEmail(HelloWorld, {
name: "User",
});
And thats it! Enjoy creating type safe emails with React and MJML!