Skip to content
Edge Functions

使用 React Email 和 Resend 自定义认证邮件

使用 发送邮件钩子 在 Supabase Edge Functions 中通过 React Email 和 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:

确保你已经安装了最新版本的 Supabase CLI。

🌐 Make sure you have the latest version of the Supabase CLI installed.

1. 创建 Supabase 函数 #

🌐 1. Create Supabase function

在本地创建一个新函数:

🌐 Create a new function locally:

supabase functions new send-email

2. 编辑处理函数 #

🌐 2. Edit the handler function

把以下代码粘贴到 index.ts 文件里:

🌐 Paste the following code into the index.ts file:

import { renderAsync } from 'npm:@react-email/components@^1'
import { withSupabase } from 'npm:@supabase/server@^1'
import React from 'npm:react@^19'
import { Resend } from 'npm:resend@^6'
import { Webhook } from 'npm:standardwebhooks@^1'
import { MagicLinkEmail } from './_templates/magic-link.tsx'
const resend = new Resend(Deno.env.get('RESEND_API_KEY') as string)
const hookSecret = (Deno.env.get('SEND_EMAIL_HOOK_SECRET') as string).replace('v1,whsec_', '')
export default {
fetch: withSupabase({ auth: 'none' }, async (req) => {
if (req.method !== 'POST') {
return Response.json({ error: 'not allowed' }, { status: 400 })
}
const payload = await req.text()
const headers = Object.fromEntries(req.headers)
const wh = new Webhook(hookSecret)
try {
const {
user,
email_data: { token, token_hash, redirect_to, email_action_type },
} = wh.verify(payload, headers) as {
user: {
email: string
}
email_data: {
token: string
token_hash: string
redirect_to: string
email_action_type: string
site_url: string
token_new: string
token_hash_new: string
}
}
const html = await renderAsync(
React.createElement(MagicLinkEmail, {
supabase_url: Deno.env.get('SUPABASE_URL') ?? '',
token,
token_hash,
redirect_to,
email_action_type,
})
)
const { error } = await resend.emails.send({
from: 'welcome <onboarding@resend.dev>',
to: [user.email],
subject: 'Supa Custom MagicLink!',
html,
})
if (error) {
throw error
}
} catch (error) {
console.log(error)
return Response.json(
{
error: {
http_code: error.code,
message: error.message,
},
},
{ status: 401 }
)
}
return Response.json({})
}),
}

3. 创建 React 邮件模板 #

🌐 3. Create React Email templates

按照推荐的项目结构创建一个新的_templates文件夹,并添加一个新的magic-link.tsx文件,内容如下:

🌐 Create a new _templates folder following the recommended project structure and add a new magic-link.tsx file with the following code:

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

4. 部署函数 #

🌐 4. Deploy the Function

将函数部署到 Supabase:

🌐 Deploy function to Supabase:

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

记下这个函数的 URL,下一步你会用到它!

🌐 Note down the function URL, you will need it in the next step!

5. 配置发送邮件钩子 #

🌐 5. Configure the Send Email Hook

  • 去 Supabase 控制面板的 Auth Hooks 部分,创建一个新的“发送邮件钩子”。
  • 选择 HTTPS 作为钩子类型。
  • 把函数的 URL 粘贴到“URL”字段里。
  • 点击“生成密钥”来生成你的 webhook 密钥并记下来。
  • 点击“创建”以保存钩子配置。

把这些秘密存到你的 .env 文件里。

🌐 Store these secrets in your .env file.

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

从 .env 文件中设置秘密:

🌐 Set the secrets from the .env file:

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

现在,每当需要向用户发送认证邮件时,你的 Supabase Edge 函数都会被触发!

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

更多资源 #

🌐 More resources