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:

1
supabase functions new send-email

2. Edit the handler function#

Paste the following code into the index.ts file:

1
import React from 'npm:react@18.3.1'
2
import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0'
3
import { Resend } from 'npm:resend@4.0.0'
4
import { renderAsync } from 'npm:@react-email/components@0.0.22'
5
import { MagicLinkEmail } from './_templates/magic-link.tsx'
6
7
const resend = new Resend(Deno.env.get('RESEND_API_KEY') as string)
8
const hookSecret = (Deno.env.get('SEND_EMAIL_HOOK_SECRET') as string).replace('v1,whsec_', '')
9
10
Deno.serve(async (req) => {
11
if (req.method !== 'POST') {
12
return new Response('not allowed', { status: 400 })
13
}
14
15
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: string
25
}
26
email_data: {
27
token: string
28
token_hash: string
29
redirect_to: string
30
email_action_type: string
31
site_url: string
32
token_new: string
33
token_hash_new: string
34
}
35
}
36
37
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
)
46
47
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 error
55
}
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
}
71
72
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:

1
import {
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'
11
import * as React from 'npm:react@18.3.1'
12
13
interface MagicLinkEmailProps {
14
supabase_url: string
15
email_action_type: string
16
redirect_to: string
17
token_hash: string
18
token: string
19
}
20
21
export 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
<Link
35
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 link
44
</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
<Text
50
style={{
51
...text,
52
color: '#ababab',
53
marginTop: '14px',
54
marginBottom: '16px',
55
}}
56
>
57
If you didn&apos;t try to login, you can safely ignore this email.
58
</Text>
59
<Text style={footer}>
60
<Link
61
href="https://demo.vercel.store/"
62
target="_blank"
63
style={{ ...link, color: '#898989' }}
64
>
65
ACME Corp
66
</Link>
67
, the famouse demo corp.
68
</Text>
69
</Container>
70
</Body>
71
</Html>
72
)
73
74
export default MagicLinkEmail
75
76
const main = {
77
backgroundColor: '#ffffff',
78
}
79
80
const container = {
81
paddingLeft: '12px',
82
paddingRight: '12px',
83
margin: '0 auto',
84
}
85
86
const 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
}
95
96
const 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
}
103
104
const 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
}
111
112
const 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
}
121
122
const 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
}

4. Deploy the Function#

Deploy function to Supabase:

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

1
RESEND_API_KEY=your_resend_api_key
2
SEND_EMAIL_HOOK_SECRET="v1,whsec_<base64_secret>"

Set the secrets from the .env file:

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

Now your Supabase Edge Function will be triggered anytime an Auth Email needs to be sent to the user!

More resources#