Edge Functions

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.

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:


_10
supabase functions new send-email

2. Edit the handler function

Paste the following code into the index.ts file:

supabase/functions/send-email/index.ts

_78
import React from 'npm:react@18.3.1'
_78
import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0'
_78
import { Resend } from 'npm:resend@4.0.0'
_78
import { renderAsync } from 'npm:@react-email/components@0.0.22'
_78
import { MagicLinkEmail } from './_templates/magic-link.tsx'
_78
_78
const resend = new Resend(Deno.env.get('RESEND_API_KEY') as string)
_78
const hookSecret = Deno.env.get('SEND_EMAIL_HOOK_SECRET') as string
_78
_78
Deno.serve(async (req) => {
_78
if (req.method !== 'POST') {
_78
return new Response('not allowed', { status: 400 })
_78
}
_78
_78
const payload = await req.text()
_78
const headers = Object.fromEntries(req.headers)
_78
const wh = new Webhook(hookSecret)
_78
try {
_78
const {
_78
user,
_78
email_data: { token, token_hash, redirect_to, email_action_type },
_78
} = wh.verify(payload, headers) as {
_78
user: {
_78
email: string
_78
}
_78
email_data: {
_78
token: string
_78
token_hash: string
_78
redirect_to: string
_78
email_action_type: string
_78
site_url: string
_78
token_new: string
_78
token_hash_new: string
_78
}
_78
}
_78
_78
const html = await renderAsync(
_78
React.createElement(MagicLinkEmail, {
_78
supabase_url: Deno.env.get('SUPABASE_URL') ?? '',
_78
token,
_78
token_hash,
_78
redirect_to,
_78
email_action_type,
_78
})
_78
)
_78
_78
const { error } = await resend.emails.send({
_78
from: 'welcome <onboarding@resend.dev>',
_78
to: [user.email],
_78
subject: 'Supa Custom MagicLink!',
_78
html,
_78
})
_78
if (error) {
_78
throw error
_78
}
_78
} catch (error) {
_78
console.log(error)
_78
return new Response(
_78
JSON.stringify({
_78
error: {
_78
http_code: error.code,
_78
message: error.message,
_78
},
_78
}),
_78
{
_78
status: 401,
_78
headers: { 'Content-Type': 'application/json' },
_78
}
_78
)
_78
}
_78
_78
const responseHeaders = new Headers()
_78
responseHeaders.set('Content-Type', 'application/json')
_78
return new Response(JSON.stringify({}), {
_78
status: 200,
_78
headers: responseHeaders,
_78
})
_78
})

3. Create React Email templates

Create a new folder _templates and create a new file magic-link.tsx with the following code:

supabase/functions/send-email/_templates/magic-link.tsx

_130
import {
_130
Body,
_130
Container,
_130
Head,
_130
Heading,
_130
Html,
_130
Link,
_130
Preview,
_130
Text,
_130
} from 'npm:@react-email/components@0.0.22'
_130
import * as React from 'npm:react@18.3.1'
_130
_130
interface MagicLinkEmailProps {
_130
supabase_url: string
_130
email_action_type: string
_130
redirect_to: string
_130
token_hash: string
_130
token: string
_130
}
_130
_130
export const MagicLinkEmail = ({
_130
token,
_130
supabase_url,
_130
email_action_type,
_130
redirect_to,
_130
token_hash,
_130
}: MagicLinkEmailProps) => (
_130
<Html>
_130
<Head />
_130
<Preview>Log in with this magic link</Preview>
_130
<Body style={main}>
_130
<Container style={container}>
_130
<Heading style={h1}>Login</Heading>
_130
<Link
_130
href={`${supabase_url}/auth/v1/verify?token=${token_hash}&type=${email_action_type}&redirect_to=${redirect_to}`}
_130
target="_blank"
_130
style={{
_130
...link,
_130
display: 'block',
_130
marginBottom: '16px',
_130
}}
_130
>
_130
Click here to log in with this magic link
_130
</Link>
_130
<Text style={{ ...text, marginBottom: '14px' }}>
_130
Or, copy and paste this temporary login code:
_130
</Text>
_130
<code style={code}>{token}</code>
_130
<Text
_130
style={{
_130
...text,
_130
color: '#ababab',
_130
marginTop: '14px',
_130
marginBottom: '16px',
_130
}}
_130
>
_130
If you didn&apos;t try to login, you can safely ignore this email.
_130
</Text>
_130
<Text style={footer}>
_130
<Link
_130
href="https://demo.vercel.store/"
_130
target="_blank"
_130
style={{ ...link, color: '#898989' }}
_130
>
_130
ACME Corp
_130
</Link>
_130
, the famouse demo corp.
_130
</Text>
_130
</Container>
_130
</Body>
_130
</Html>
_130
)
_130
_130
export default MagicLinkEmail
_130
_130
const main = {
_130
backgroundColor: '#ffffff',
_130
}
_130
_130
const container = {
_130
paddingLeft: '12px',
_130
paddingRight: '12px',
_130
margin: '0 auto',
_130
}
_130
_130
const h1 = {
_130
color: '#333',
_130
fontFamily:
_130
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
_130
fontSize: '24px',
_130
fontWeight: 'bold',
_130
margin: '40px 0',
_130
padding: '0',
_130
}
_130
_130
const link = {
_130
color: '#2754C5',
_130
fontFamily:
_130
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
_130
fontSize: '14px',
_130
textDecoration: 'underline',
_130
}
_130
_130
const text = {
_130
color: '#333',
_130
fontFamily:
_130
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
_130
fontSize: '14px',
_130
margin: '24px 0',
_130
}
_130
_130
const footer = {
_130
color: '#898989',
_130
fontFamily:
_130
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
_130
fontSize: '12px',
_130
lineHeight: '22px',
_130
marginTop: '12px',
_130
marginBottom: '24px',
_130
}
_130
_130
const code = {
_130
display: 'inline-block',
_130
padding: '16px 4.5%',
_130
width: '90.5%',
_130
backgroundColor: '#f4f4f4',
_130
borderRadius: '5px',
_130
border: '1px solid #eee',
_130
color: '#333',
_130
}

4. Deploy the Function

Deploy function to Supabase:


_10
supabase functions deploy send-email --no-verify-jwt

Note 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.

supabase/functions/.env

_10
RESEND_API_KEY=your_resend_api_key
_10
SEND_EMAIL_HOOK_SECRET=<base64_secret>

Set the secrets from the .env file:


_10
supabase secrets set --env-file supabase/functions/.env

That's it, now your Supabase Edge Function will be triggered anytime an Auth Email needs to be send to the user!

More resources