边缘函数

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


使用发送邮件钩子在Supabase边缘函数中通过React EmailResend发送自定义认证邮件。

前提条件

为了充分利用本指南,您需要:

确保已安装最新版本的Supabase CLI

1. 创建Supabase函数

在本地创建新函数:

1
supabase functions new send-email

2. 编辑处理函数

将以下代码粘贴到 index.ts 文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import 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 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import { 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',}

4. 部署函数

将函数部署到 Supabase:

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

记下函数 URL,下一步将会用到它!

5. 配置发送邮件钩子

  • 进入 Supabase 仪表板的 Auth Hooks 部分,创建一个新的"发送邮件钩子"
  • 选择 HTTPS 作为钩子类型
  • 将函数 URL 粘贴到"URL"字段中
  • 点击"生成密钥"来生成您的 webhook 密钥并记录下来
  • 点击"创建"保存钩子配置

将这些密钥存储在您的 .env 文件中:

1
2
RESEND_API_KEY=your_resend_api_keySEND_EMAIL_HOOK_SECRET=<base64_secret>

.env 文件设置密钥:

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

现在,每当需要向用户发送认证邮件时,您的 Supabase 边缘函数就会被触发!

更多资源