Skip to content

Email development

If you are starting from an example project - all emails are installed by default. Therefore, you can continue reviewing the rest of the information.

Run a local email development server using React Email

We use React Email, a collection of high-quality, unstyled components for creating beautiful emails using React and TypeScript. React Email also has a CLI that is able to start a local development server for email development. Here's how to integrate it into your existing project.

bash
# Add dependencies
pnpm add react @react-email/components

# Add dev dependencies
pnpm add -D react-email

With these dependencies we will be able to create, develop and test our emails using React Email's development server and use their CLI to generate HTML files for our Supabase project.

Again, here are some commands you can add in your package.json for ease-of-use:

json
"scripts": {
  "email:dev": "email dev -p 4000",
  "email:export": "email export --outDir supabase/templates --pretty",
}

WARNING

Before you run any of these commands make sure to create an ./emails directory.

Set your email templates

bash
degit webscopeio/supabase-modules/apps/next/emails emails
ts
import * as React from "react"

export const main: React.CSSProperties = {
  backgroundColor: "#ffffff",
  fontFamily:
    '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
}

export const container: React.CSSProperties = {
  margin: "0 auto",
  padding: "20px 12px 48px",
  maxWidth: "600px",
}

export const h1: React.CSSProperties = {
  color: "#09090B",
  fontSize: "24px",
  fontWeight: "bold",
  margin: "40px 0 16px",
  padding: "0",
}

export const buttonContainer: React.CSSProperties = {
  textAlign: "center",
}

export const button: React.CSSProperties = {
  backgroundColor: "#18181B",
  color: "#FAFAFA",
  fontWeight: 500,
  fontSize: "14px",
  padding: "10.5px 18px",
  borderRadius: "8px",
  width: "fit-content",
  textDecoration: "none",
  textAlign: "center",
  display: "block",
}

export const code: React.CSSProperties = {
  display: "inline-block",
  padding: "16px 4.5%",
  width: "90.5%",
  backgroundColor: "#F4F4F5",
  borderRadius: "5px",
  border: "1px solid #eee",
  color: "#09090B",
}

export const link: React.CSSProperties = {
  color: "#2754C5",
  fontSize: "14px",
  textDecoration: "underline",
}

export const text: React.CSSProperties = {
  color: "#09090B",
  fontSize: "14px",
  margin: "16px 0",
}

export const hr: React.CSSProperties = {
  borderColor: "#dfe1e4",
  margin: "24px 0",
}

export const footer: React.CSSProperties = {
  color: "#898989",
  fontSize: "12px",
  lineHeight: "22px",
  marginTop: "12px",
  marginBottom: "24px",
}
tsx
import * as React from "react"
import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Hr,
  Html,
  Link,
  Preview,
  Section,
  Text,
} from "@react-email/components"
import { EmailOtpType } from "@supabase/supabase-js"

import * as styles from "./_shared/styles"

const type: EmailOtpType = "signup"
const confirmationURL = `{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=${type}`

export default function Email() {
  return (
    <Html>
      <Head />
      <Preview>Confirm your Sign up</Preview>
      <Body style={styles.main}>
        <Container style={styles.container}>
          <Heading style={styles.h1}>Confirm your Sign up</Heading>
          <Text style={styles.text}>
            Click on <b>Sign up</b> or use a <b>One-time password</b>:
          </Text>
          <Section style={styles.buttonContainer}>
            <Button style={styles.button} href={confirmationURL}>
              Sign up
            </Button>
          </Section>
          <Text>
            <code style={styles.code}>{`{{ .Token }}`}</code>
          </Text>
          <Text
            style={{
              ...styles.text,
              color: "#71717A",
            }}
          >
            If you didn&apos;t try to create an account, you can safely ignore
            this email.
          </Text>
          <Hr style={styles.hr} />
          <Text style={styles.footer}>
            <Link
              href="https://supabase-modules-docs.vercel.app"
              target="_blank"
              style={{ ...styles.link, color: "#71717A" }}
            >
              Supabase Modules
            </Link>
            <br />
            Build smarter with pre-built modules today
          </Text>
        </Container>
      </Body>
    </Html>
  )
}
tsx
import * as React from "react"
import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Hr,
  Html,
  Link,
  Preview,
  Section,
  Text,
} from "@react-email/components"
import { EmailOtpType } from "@supabase/supabase-js"

import * as styles from "./_shared/styles"

const type: EmailOtpType = "email_change"
const confirmationURL = `{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=${type}`

export default function Email() {
  return (
    <Html>
      <Head />
      <Preview>Confirm email</Preview>
      <Body style={styles.main}>
        <Container style={styles.container}>
          <Heading style={styles.h1}>Confirm email</Heading>
          {`{{ if .Email }}`}
          <Text style={styles.text}>
            Click on <b>Confirm email</b> to update your email from{" "}
            {`{{ .Email }}`} to {`{{ .NewEmail }}`}
          </Text>
          {`{{ else }}`}
          <Text style={styles.text}>
            Click on <b>Confirm email</b> to set your email to{" "}
            {`{{ .NewEmail }}`}
          </Text>
          {`{{ end }}`}
          <Section style={styles.buttonContainer}>
            <Button style={styles.button} href={confirmationURL}>
              Confirm email
            </Button>
          </Section>
          <Text
            style={{
              ...styles.text,
              color: "#71717A",
            }}
          >
            If you didn&apos;t request an email change, you can safely ignore
            this email.
          </Text>
          <Hr style={styles.hr} />
          <Text style={styles.footer}>
            <Link
              href="https://supabase-modules-docs.vercel.app"
              target="_blank"
              style={{ ...styles.link, color: "#71717A" }}
            >
              Supabase Modules
            </Link>
            <br />
            Build smarter with pre-built modules today
          </Text>
        </Container>
      </Body>
    </Html>
  )
}
tsx
import * as React from "react"
import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Hr,
  Html,
  Link,
  Preview,
  Section,
  Text,
} from "@react-email/components"
import { EmailOtpType } from "@supabase/supabase-js"

import * as styles from "./_shared/styles"

const type: EmailOtpType = "invite"
const confirmationURL = `{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=${type}`

export default function Email() {
  return (
    <Html>
      <Head />
      <Preview>You have been invited</Preview>
      <Body style={styles.main}>
        <Container style={styles.container}>
          <Heading style={styles.h1}>You have been invited</Heading>
          <Text style={styles.text}>
            Click on <b>Accept Invite</b> or use a <b>One-time password</b>:
          </Text>
          <Section style={styles.buttonContainer}>
            <Button style={styles.button} href={confirmationURL}>
              Accept Invite
            </Button>
          </Section>
          <Text>
            <code style={styles.code}>{`{{ .Token }}`}</code>
          </Text>
          <Text
            style={{
              ...styles.text,
              color: "#71717A",
            }}
          >
            If you don&apos;t recognize this invite, you can safely ignore this
            email.
          </Text>
          <Hr style={styles.hr} />
          <Text style={styles.footer}>
            <Link
              href="https://supabase-modules-docs.vercel.app"
              target="_blank"
              style={{ ...styles.link, color: "#71717A" }}
            >
              Supabase Modules
            </Link>
            <br />
            Build smarter with pre-built modules today
          </Text>
        </Container>
      </Body>
    </Html>
  )
}
tsx
import * as React from "react"
import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Hr,
  Html,
  Link,
  Preview,
  Section,
  Text,
} from "@react-email/components"
import { EmailOtpType } from "@supabase/supabase-js"

import * as styles from "./_shared/styles"

const type: EmailOtpType = "magiclink"
const confirmationURL = `{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=${type}`

export default function Email() {
  return (
    <Html>
      <Head />
      <Preview>Passwordless Sign in</Preview>
      <Body style={styles.main}>
        <Container style={styles.container}>
          <Heading style={styles.h1}>Passwordless Sign in</Heading>
          <Text style={styles.text}>
            Click on <b>Sign in</b> or use a <b>One-time password</b>:
          </Text>
          <Section style={styles.buttonContainer}>
            <Button style={styles.button} href={confirmationURL}>
              Sign in
            </Button>
          </Section>
          <Text>
            <code style={styles.code}>{`{{ .Token }}`}</code>
          </Text>
          <Text
            style={{
              ...styles.text,
              color: "#71717A",
            }}
          >
            If you didn&apos;t try to sign in, you can safely ignore this email.
          </Text>
          <Hr style={styles.hr} />
          <Text style={styles.footer}>
            <Link
              href="https://supabase-modules-docs.vercel.app"
              target="_blank"
              style={{ ...styles.link, color: "#71717A" }}
            >
              Supabase Modules
            </Link>
            <br />
            Build smarter with pre-built modules today
          </Text>
        </Container>
      </Body>
    </Html>
  )
}
tsx
import * as React from "react"
import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Hr,
  Html,
  Link,
  Preview,
  Section,
  Text,
} from "@react-email/components"
import { EmailOtpType } from "@supabase/supabase-js"

import * as styles from "./_shared/styles"

const type: EmailOtpType = "recovery"
const confirmationURL = `{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=${type}`

export default function Email() {
  return (
    <Html>
      <Head />
      <Preview>Reset your password</Preview>
      <Body style={styles.main}>
        <Container style={styles.container}>
          <Heading style={styles.h1}>Reset your password</Heading>
          <Text style={styles.text}>
            Click on <b>Password Reset</b> or use a <b>One-time password</b>:
          </Text>
          <Section style={styles.buttonContainer}>
            <Button style={styles.button} href={confirmationURL}>
              Password Reset
            </Button>
          </Section>
          <Text>
            <code style={styles.code}>{`{{ .Token }}`}</code>
          </Text>
          <Text
            style={{
              ...styles.text,
              color: "#71717A",
            }}
          >
            If you didn&apos;t try to reset your password, you can safely ignore
            this email.
          </Text>
          <Hr style={styles.hr} />
          <Text style={styles.footer}>
            <Link
              href="https://supabase-modules-docs.vercel.app"
              target="_blank"
              style={{ ...styles.link, color: "#71717A" }}
            >
              Supabase Modules
            </Link>
            <br />
            Build smarter with pre-built modules today
          </Text>
        </Container>
      </Body>
    </Html>
  )
}

Set your CLI configuration to include your email templates

You can customize all of the authentication emails in your CLI configuration using the config.toml file. This guide is only showcasing the invite email but you can read more about the other available email templates in Customizing Email Templates.

toml
[auth.email]
# Allow/disallow new user signups via email to your project.
enable_signup = true
# If enabled, a user will be required to confirm any email change on both the old, and new email
# addresses. If disabled, only the new email is required to confirm.
double_confirm_changes = true
# If enabled, users need to confirm their email address before signing in.
enable_confirmations = true

[auth.email.template.invite]
subject = "You have been invited"
content_path = "./supabase/templates/invite.html"

Remember, you will need to reset or restart your containers whenever the CLI configuration changes.

Use Inbucket to capture emails sent from your local machine

Now you can use Inbucket to test the email templates, use different email addresses, see how the variables are being loaded into the emails and test the different authentication flows. You should be able to see both the generated HTML and Plain Text from your email templates.

These settings are only for local development. To update your hosted project, please copy the templates from supabase/templates into the Email Templates section of the Dashboard.

Keep in mind that for your production application you should update your SMTP configuration to use a service such as Resend. You can read more about this process in their guide Supabase with SMTP