Custom Auth Emails with React Email and Resend
Use the send email hook to send custom auth emails with React Email and Resend in Supabase Edge Functions.
Prefer to jump straight to the code? Check out the example on GitHub.
Prerequisites
To get the most out of this guide, you’ll need to:
Make sure you have the latest version of the Supabase CLI installed.
1. Create Supabase function
Create a new function locally:
1supabase functions new send-email2. Edit the handler function
Paste the following code into the index.ts file:
1import React from 'npm:react@18.3.1'2import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0'3import { Resend } from 'npm:resend@4.0.0'4import { renderAsync } from 'npm:@react-email/components@0.0.22'5import { MagicLinkEmail } from './_templates/magic-link.tsx'67const resend = new Resend(Deno.env.get('RESEND_API_KEY') as string)8const hookSecret = Deno.env.get('SEND_EMAIL_HOOK_SECRET') as string910Deno.serve(async (req) => {11 if (req.method !== 'POST') {12 return new Response('not allowed', { status: 400 })13 }1415 const payload = await req.text()16 const headers = Object.fromEntries(req.headers)17 const wh = new Webhook(hookSecret)18 try {19 const {20 user,21 email_data: { token, token_hash, redirect_to, email_action_type },22 } = wh.verify(payload, headers) as {23 user: {24 email: string25 }26 email_data: {27 token: string28 token_hash: string29 redirect_to: string30 email_action_type: string31 site_url: string32 token_new: string33 token_hash_new: string34 }35 }3637 const html = await renderAsync(38 React.createElement(MagicLinkEmail, {39 supabase_url: Deno.env.get('SUPABASE_URL') ?? '',40 token,41 token_hash,42 redirect_to,43 email_action_type,44 })45 )4647 const { error } = await resend.emails.send({48 from: 'welcome <onboarding@resend.dev>',49 to: [user.email],50 subject: 'Supa Custom MagicLink!',51 html,52 })53 if (error) {54 throw error55 }56 } catch (error) {57 console.log(error)58 return new Response(59 JSON.stringify({60 error: {61 http_code: error.code,62 message: error.message,63 },64 }),65 {66 status: 401,67 headers: { 'Content-Type': 'application/json' },68 }69 )70 }7172 const responseHeaders = new Headers()73 responseHeaders.set('Content-Type', 'application/json')74 return new Response(JSON.stringify({}), {75 status: 200,76 headers: responseHeaders,77 })78})3. Create React Email templates
Create a new folder _templates and create a new file magic-link.tsx with the following code:
1import {2 Body,3 Container,4 Head,5 Heading,6 Html,7 Link,8 Preview,9 Text,10} from 'npm:@react-email/components@0.0.22'11import * as React from 'npm:react@18.3.1'1213interface MagicLinkEmailProps {14 supabase_url: string15 email_action_type: string16 redirect_to: string17 token_hash: string18 token: string19}2021export const MagicLinkEmail = ({22 token,23 supabase_url,24 email_action_type,25 redirect_to,26 token_hash,27}: MagicLinkEmailProps) => (28 <Html>29 <Head />30 <Preview>Log in with this magic link</Preview>31 <Body style={main}>32 <Container style={container}>33 <Heading style={h1}>Login</Heading>34 <Link35 href={`${supabase_url}/auth/v1/verify?token=${token_hash}&type=${email_action_type}&redirect_to=${redirect_to}`}36 target="_blank"37 style={{38 ...link,39 display: 'block',40 marginBottom: '16px',41 }}42 >43 Click here to log in with this magic link44 </Link>45 <Text style={{ ...text, marginBottom: '14px' }}>46 Or, copy and paste this temporary login code:47 </Text>48 <code style={code}>{token}</code>49 <Text50 style={{51 ...text,52 color: '#ababab',53 marginTop: '14px',54 marginBottom: '16px',55 }}56 >57 If you didn't try to login, you can safely ignore this email.58 </Text>59 <Text style={footer}>60 <Link61 href="https://demo.vercel.store/"62 target="_blank"63 style={{ ...link, color: '#898989' }}64 >65 ACME Corp66 </Link>67 , the famouse demo corp.68 </Text>69 </Container>70 </Body>71 </Html>72)7374export default MagicLinkEmail7576const main = {77 backgroundColor: '#ffffff',78}7980const container = {81 paddingLeft: '12px',82 paddingRight: '12px',83 margin: '0 auto',84}8586const h1 = {87 color: '#333',88 fontFamily:89 "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",90 fontSize: '24px',91 fontWeight: 'bold',92 margin: '40px 0',93 padding: '0',94}9596const link = {97 color: '#2754C5',98 fontFamily:99 "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",100 fontSize: '14px',101 textDecoration: 'underline',102}103104const text = {105 color: '#333',106 fontFamily:107 "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",108 fontSize: '14px',109 margin: '24px 0',110}111112const footer = {113 color: '#898989',114 fontFamily:115 "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",116 fontSize: '12px',117 lineHeight: '22px',118 marginTop: '12px',119 marginBottom: '24px',120}121122const code = {123 display: 'inline-block',124 padding: '16px 4.5%',125 width: '90.5%',126 backgroundColor: '#f4f4f4',127 borderRadius: '5px',128 border: '1px solid #eee',129 color: '#333',130}You can find a selection of React Email templates in the React Email Examples.
4. Deploy the Function
Deploy function to Supabase:
1supabase functions deploy send-email --no-verify-jwtNote down the function URL, you will need it in the next step!
5. Configure the Send Email Hook
- Go to the Auth Hooks section of the Supabase dashboard and create a new "Send Email hook".
- Select HTTPS as the hook type.
- Paste the function URL in the "URL" field.
- Click "Generate Secret" to generate your webhook secret and note it down.
- Click "Create" to save the hook configuration.
Store these secrets in your .env file.
1RESEND_API_KEY=your_resend_api_key2SEND_EMAIL_HOOK_SECRET=<base64_secret>You can generate the secret in the Auth Hooks section of the Supabase dashboard. Make sure to remove the v1,whsec_ prefix!
Set the secrets from the .env file:
1supabase secrets set --env-file supabase/functions/.envNow your Supabase Edge Function will be triggered anytime an Auth Email needs to be sent to the user!