See the link below for an informal video covering the steps below and exploring the addition of a second renderer.

https://www.youtube.com/watch?v=1-ICr-J_YN0

1. Creating an initial api/src/lib/mailer.ts

Let’s create our redwood mailer. Not all of this config below is needed as I’m trying to be verbose to cover the available options.

// mailer-core provides the Mailer itself
import { Mailer } from '@redwoodjs/mailer-core'

// These handler classes are who take your email content and send it out into the
// world. Think nodemailer for connecting to some SMTP server, amazon SES, etc.
import { InMemoryMailHandler } from '@redwoodjs/mailer-handler-in-memory'
import { NodemailerMailHandler } from '@redwoodjs/mailer-handler-nodemailer'
import { StudioMailHandler } from '@redwoodjs/mailer-handler-studio'

// The renderer is responsible for taking your react components and transforming
// them into valid email html/text which your handler will expect
import { ReactEmailRenderer } from '@redwoodjs/mailer-renderer-react-email'

// The normal redwood logger as you'd see in db.ts or auth.ts
import { logger } from './logger'

// We have to export a mailer variable which is an instance of the Mailer class.
// This class takes one config object as a constructor parameter
export const mailer = new Mailer({
  // handling is dealing with your handlers
  handling: {
		// handlers is a key:value mapping of all the handlers you want to use
		// the key can take anyname but we'll just use the handler name here.
    handlers: {
      inMemory: new InMemoryMailHandler(),
      studio: new StudioMailHandler(),
      nodemailer: new NodemailerMailHandler({
				// We can see the nodemailer handler takes the typical parameters 
				// that nodemailer might need to make an SMTP connection
        transport: {
          host: 'localhost',
          port: 4319,
          secure: false,
        },
      }),
    },
		// we specify a default handler - this is our default in production!
    default: 'nodemailer',
  },

	// rendering is dealing with your renderers
  rendering: {
		// again another key:value mapping just like handlers
    renderers: {
      reactEmail: new ReactEmailRenderer(),
    },

		// this is your default renderer - this isn't specific to prod/dev/test
    default: 'reactEmail',
  },

	// test is used to provide some options that are relevant only when your
	// mailer is in test mode
  test: {
		// the handler to use when in test mode
    handler: 'inMemory',

		// the condition to determine if we should be in test mode
		// this could also be a sync function
    when: process.env.NODE_ENV === 'test',
  },

	// similar to test above we specify development mode options
  development: {
		// the handler to use in development
    handler: 'studio',

		// when are we in development
		// we should make this as wide as possible so we only reach production
		// mode when we really want to be
    when: process.env.NODE_ENV !== 'production',
  },

	// these are your default send options that will be used if you don't provide
	// them at the time you go to send emails
  defaults: {
    replyTo: '[email protected]',
    from: '[email protected]',
  },

	// the normal redwood logger is passed in
  logger,
})

2. Creating a mail template component: api/src/mail/**/*.(tsx|jsx)

Let’s create an example email template using react-email and their components.

import React from 'react'

import {
  Html,
  Text,
  Hr,
  Body,
  Head,
  Tailwind,
  Preview,
  Container,
  Section,
  Img,
  Heading,
  Link,
} from '@react-email/components'

export function ContactUsEmail({
  name,
  email,
  message,
  when,
}: {
  name: string
  email: string
  message: string
  when: string
}) {
  return (
    <Html lang="en">
      <Head />
      <Preview>Message from: {email}</Preview>
      <Tailwind>
        <Body className="mx-auto my-auto bg-white font-sans">
          <Container className="mx-auto my-[40px] rounded border border-solid border-gray-200 p-[20px]">
            <Section className="mt-[32px]">
              <Img
                src={'<https://redwoodjs.com/images/logo.svg>'}
                width="40"
                height="37"
                alt="RedwoodJS"
                className="mx-auto my-0"
              />
            </Section>
            <Heading className="mx-0 my-[30px] p-0 text-center text-[24px] font-normal text-black">
              New Message
            </Heading>
            <Text className="text-[14px] leading-[24px] text-black">
              A user called &ldquo;{name}&rdquo; has sent the following message:
            </Text>
            <Container className="mx-0 my-0 bg-gray-100 px-4">
              <Text className="text-[14px] text-black">{message}</Text>
            </Container>
            <Text className="text-[14px] leading-[24px] text-black">
              They can be replied to by email at:{' '}
              <Link
                href={`mailto:${email}`}
                className="text-blue-600 no-underline"
              >
                {email}
              </Link>
            </Text>
            <Hr className="mx-0 my-[26px] w-full border border-solid border-[#eaeaea]" />
            <Text className="text-[12px] leading-[24px] text-[#666666]">
              Message was sent on {when}
            </Text>
          </Container>
        </Body>
      </Tailwind>
    </Html>
  )
}

We also have to update our tsconfig or jsconfig on the api side to include:

"jsx": "react-jsx"

3. Iterating on my template: studio - template preview

It’s possible to view a live preview of your mail template from within studio and it’s “Template Preview” page. Here you can select the appropriate template and if needed the particular exported component or renderer. You can also pass props (limited to JSON) which your email component might take.

Untitled

4. Sending mail in a service: api/src/services/contacts/contacts.ts

Now lets send an email in our service.

import { mailer } from 'src/lib/mailer'
import { ContactUsEmail } from 'src/mail/ContactUs'
...
...
...
export const createContact: MutationResolvers['createContact'] = async ({
  input,
}) => {
  const contact = db.contact.create({
    data: input,
  })

  // Send an email
  await mailer.send(
    ContactUsEmail({
      name: input.name,
      email: input.email,
      message: input.message,
      when: new Date().toLocaleString(),
    }),
    {
      to: '[email protected]',
      subject: 'New contact form submission',
    }
  )

  return contact
}