Docs
Email
Setup for sending emails with React components.
Had to comment out the component code because of some "fs" module error bullshit.
Installation
Install the following dependencies:
pnpm add jsx-email nodemailer
pnpm add @types/nodemailer -D
Add to next.config.js
to prevent .mjs bundle errors.
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config) => {
config.module.rules.push({
test: /\.m?js$/,
type: "javascript/auto",
resolve: {
fullySpecified: false,
},
});
return config;
},
experimental: {
esmExternals: false,
},
};
module.exports = nextConfig;
Create and configure lib/mailer.ts
import type { ReactElement } from "react";
import { render } from "jsx-email";
import type { SentMessageInfo } from "nodemailer";
import nodemailer from "nodemailer";
interface Mail {
from?: string;
to: string | string[];
subject: string;
html?: string;
text?: string;
component?: ReactElement;
}
type MailSentState = {
info: SentMessageInfo;
} & (
| {
ok: true;
}
| {
ok: false;
error: Error;
}
);
/**
*
* @param mail
* @returns
* @throws Error
*/
export async function sendMail(mail: Mail) {
const { from, html, component, ...restMail } = mail;
const transporter = nodemailer.createTransport(process.env.MAILER);
let htmlContent: string | undefined = undefined;
if (component) {
htmlContent = (await render(component)).trim();
} else if (html) {
htmlContent = html.trim();
}
return await new Promise<MailSentState>((res) => {
transporter.sendMail(
{
from: from ?? '"Max Mustermann" <no-reply@mustermann.de>',
html: htmlContent,
...restMail,
},
(err, info) => {
if (err) {
res({
ok: false,
error: err,
info,
});
} else {
res({
ok: true,
info,
});
}
},
);
});
}
Create components/email.tsx
/* import {
Body,
Head,
Html,
Link,
Button as MailButton,
Container as MailContainer,
Heading as MailHeading,
Section as MailSection,
Text as MailText,
Preview,
} from "jsx-email";
export const Base = ({
children,
preview,
}: {
children?: React.ReactNode;
preview: string;
}) => {
return (
<Html>
<Head />
<Preview>{preview}</Preview>
<Body
style={{
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
backgroundColor: "#f6f9fc",
paddingTop: "64px",
}}
>
{children}
<MailContainer
style={{
margin: "0 auto",
marginBottom: "64px",
padding: "20px 0 48px",
fontSize: "0.6rem",
color: "#6b7280",
textAlign: "center",
}}
>
Max Mustermann GmbH
<br />
Musterstraße 12, 06849 Musterstadt
<br />
Geschäftsführer: Max Mustermann
<br />
Amtsgericht Muster
<br />
USt-IdNr. Muster Kontakt:{" "}
<Link href={"mailto:support@feierstoff.de"}>muster-email</Link> <br />
Tel: 0341 -
</MailContainer>
</Body>
</Html>
);
};
export const Button = ({
children,
href,
}: {
children: React.ReactNode;
href?: string;
}) => {
return (
<MailButton
href={href}
style={{
padding: "12px 0",
backgroundColor: "#cc0033",
borderRadius: "4px",
fontWeight: "bolder",
textAlign: "center",
textDecoration: "none",
width: "100%",
color: "white",
display: "block",
fontSize: "14px",
}}
>
{children}
</MailButton>
);
};
export const Container = ({ children }: { children?: React.ReactNode }) => {
return (
<MailContainer
style={{
backgroundColor: "#ffffff",
margin: "0 auto",
marginBottom: "32px",
padding: "20px 0 48px",
boxShadow: "0 0.0625rem 0 #00000012",
border: "1px solid #e5e7eb",
borderRadius: "8px",
}}
>
{children}
</MailContainer>
);
};
export const Heading = ({ children }: { children?: React.ReactNode }) => {
return (
<MailHeading
style={{
color: "black",
fontSize: "24px",
padding: "0 48px",
}}
>
<strong>{children}</strong>
</MailHeading>
);
};
export const Section = ({ children }: { children: React.ReactNode }) => {
return (
<MailSection
style={{
padding: "0 48px",
}}
>
{children}
</MailSection>
);
};
export const Text = ({
children,
size = "default",
}: {
children: React.ReactNode;
size?: "default" | "sm";
}) => {
let fontSize;
switch (size) {
case "default":
fontSize = "14px";
break;
case "sm":
fontSize = "12px";
break;
}
return (
<MailText
style={{
color: "#000000",
fontSize,
lineHeight: "24px",
textAlign: "left",
}}
>
{children}
</MailText>
);
};
*/
Example Component
/* import { z } from "zod";
import {
Base,
Button,
Container,
Heading,
Section,
Text,
} from "@/components/email";
export const TemplateName = "ForgotPasswordEmail";
export const TemplateStruct = z.object({
firstName: z.string(),
invitationLink: z.string(),
});
export type TemplateProps = z.infer<typeof TemplateStruct>;
export const Template = ({ firstName, invitationLink }: TemplateProps) => (
<Base preview="Einladung">
<Container>
<Heading>Einladung</Heading>
<Section>
<Text>Hey {firstName},</Text>
<Text>
du wurdest dazu eingeladen, unser Tool zu nutzen. Nutze den folgenden
Button, um die Einladung anzunehmen und dich zu registrieren.
</Text>
<Button href={invitationLink}>Einladung annehmen</Button>
<Text>
Beste Grüße, <br />
Dein Mustermann Team
</Text>
</Section>
</Container>
</Base>
);
*/