使用React Email和Resend发送自定义认证邮件
使用发送邮件钩子在Supabase边缘函数中通过React Email和Resend发送自定义认证邮件。
想直接查看代码?查看GitHub上的示例。
前提条件
为了充分利用本指南,您需要:
确保已安装最新版本的Supabase CLI。
1. 创建Supabase函数
在本地创建新函数:
1supabase functions new send-email
2. 编辑处理函数
将以下代码粘贴到 index.ts
文件中:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778import React from 'npm:react@18.3.1'import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0'import { Resend } from 'npm:resend@4.0.0'import { renderAsync } from 'npm:@react-email/components@0.0.22'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 stringDeno.serve(async (req) => { if (req.method !== 'POST') { return new Response('不允许的请求方法', { 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: '欢迎 <onboarding@resend.dev>', to: [user.email], subject: 'Supa 自定义魔法链接!', html, }) if (error) { throw error } } catch (error) { console.log(error) return new Response( JSON.stringify({ error: { http_code: error.code, message: error.message, }, }), { status: 401, headers: { 'Content-Type': 'application/json' }, } ) } const responseHeaders = new Headers() responseHeaders.set('Content-Type', 'application/json') return new Response(JSON.stringify({}), { status: 200, headers: responseHeaders, })})
3. 创建 React 邮件模板
新建一个 _templates
文件夹,并创建 magic-link.tsx
文件,内容如下:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130import { Body, Container, Head, Heading, Html, Link, Preview, Text,} from 'npm:@react-email/components@0.0.22'import * as React from 'npm:react@18.3.1'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>使用此魔法链接登录</Preview> <Body style={main}> <Container style={container}> <Heading style={h1}>登录</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', }} > 点击此处使用此魔法链接登录 </Link> <Text style={{ ...text, marginBottom: '14px' }}> 或者,复制并粘贴此临时登录代码: </Text> <code style={code}>{token}</code> <Text style={{ ...text, color: '#ababab', marginTop: '14px', marginBottom: '16px', }} > 如果您并未尝试登录,可以安全忽略此邮件。 </Text> <Text style={footer}> <Link href="https://demo.vercel.store/" target="_blank" style={{ ...link, color: '#898989' }} > ACME 公司 </Link> ,著名的演示公司。 </Text> </Container> </Body> </Html>)export default MagicLinkEmailconst 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',}
您可以在 React Email 示例 中找到一系列 React Email 模板。
4. 部署函数
将函数部署到 Supabase:
1supabase functions deploy send-email --no-verify-jwt
记下函数 URL,下一步将会用到它!
5. 配置发送邮件钩子
- 进入 Supabase 仪表板的 Auth Hooks 部分,创建一个新的"发送邮件钩子"
- 选择 HTTPS 作为钩子类型
- 将函数 URL 粘贴到"URL"字段中
- 点击"生成密钥"来生成您的 webhook 密钥并记录下来
- 点击"创建"保存钩子配置
将这些密钥存储在您的 .env
文件中:
12RESEND_API_KEY=your_resend_api_keySEND_EMAIL_HOOK_SECRET=<base64_secret>
您可以在 Supabase 仪表板的 Auth Hooks 部分生成密钥。请确保移除 v1,whsec_
前缀!
从 .env
文件设置密钥:
1supabase secrets set --env-file supabase/functions/.env
现在,每当需要向用户发送认证邮件时,您的 Supabase 边缘函数就会被触发!