Docs
Email

Email

Setup for sending emails with React components.

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>
);
 */