文档 — 工作原理
最近更新:2026年8月6日
本页面已进行机器翻译,以方便您阅读。如有差异,仅法语版本具有法律效力。
1. 流程概览
预约由已登录的组织者创建,随后通过唯一的公开链接发送给受邀人。受邀人无需任何账户:通过公开页面回复,回复会立即出现在组织者的仪表盘上。
组织者(已登录)
└─ 创建预约 ──▶ appointments(status = pending,已生成 invite_token)
├─ “通过邮件发送” ──▶ 服务器函数 sendInviteEmail ──▶ Resend
└─ “复制链接” ──▶ /invite/<token>
受邀人(无账户)
└─ 打开 /invite/<token> ──▶ RPC get_invite(_token)(经过筛选的公开读取)
└─ 接受 / 拒绝 ──▶ RPC respond_to_invite(_token, _accept, _message)
└─ status = accepted | declined,responded_at = now()
组织者
└─ 仪表盘:状态徽章、回复日期、受邀人留言、统计数据2. 邀请邮件内容
邮件在服务器端由 sendInviteEmail 函数生成,并通过 Resend 发送。发件人:RESEND_FROM 的值(默认值为“Cadence <onboarding@resend.dev>”)。主题:“预约:{{title}}”.
邮件正文结构,按顺序:
- 标题“预约提议”。
- 问候语“您好 {{guestName}},”。
- 个性化留言区块(仅限 Business 组织者)。
- 加粗的预约标题,随后是按语言格式化的日期和时间段。
- 地点和描述,仅在已填写时显示。
- 指向邀请链接的绿色操作按钮“确认或拒绝”。
- 纯文本形式的链接提醒,供屏蔽按钮的邮件客户端使用。
- “Google 评价”区块(Business):企业名称、评分、评价链接、地图链接。
所有动态值在插入前都会进行 HTML 转义:组织者输入的任何内容都无法在邮件中注入标记。
可用变量
| 变量 | 来源 | 用途 |
|---|---|---|
| {{guestName}} | contacts.name | 问候语行“您好 {{guestName}},”。若未关联联系人则为空。 |
| {{title}} | appointments.title | 邮件主题“预约:{{title}}”以及正文中的加粗标题。 |
| {{dateRange}} | appointments.starts_at / ends_at | 按语言格式化:“8 月 12 日星期二,15:00 → 15:30”(Intl.DateTimeFormat)。 |
| {{location}} | appointments.location | 行“地点:…”。如果字段为空,整个区块将被省略。 |
| {{description}} | appointments.description | 显示在日期下方的自由段落。为空时省略。 |
| {{inviteLink}} | origin + /invite/ + appointments.invite_token | 邮件底部的“确认或拒绝”按钮 + 纯文本链接提醒(可复制粘贴)。 |
| {{inviteMessage}} | profiles.invite_message (Business) | 绿色个性化留言区块,仅对 Business 组织者显示。 |
| {{businessName}} / {{googleRating}} / {{googleReviewUrl}} / {{googleMapsUrl}} | profiles.* (Business) | 如已启用该选项,则在邮件页脚和邀请页面显示“Google 评价”区块。 |
发送未成功的情况
- no_email : 联系人没有邮箱地址。组织者需要复制链接并通过其他渠道发送。
- no_api_key : 发送密钥未配置。邀请链接仍会返回到界面以便手动复制。
- provider_error : 邮件服务商拒绝发送(地址无效、域名未验证等)。预约状态保持不变。
- 发送成功时,invite_sent_at 会被记录时间戳:统计数据中“已发送链接”的追踪正依赖此字段。
3. 确认屏幕(页面 /invite/<token>)
无需身份验证的公开页面,由受保护的函数 get_invite 渲染,该函数仅返回显示所需的字段。
- 加载中 : 在回复到达之前显示动画骨架屏(日历样式)。
- 链接无效或已过期 : 错误提示;不会泄露关于组织者或预约的任何信息。
- “待定”状态 : 主题、日期和时间段、时长、地点、组织者姓名、个性化留言(Business)、可选自由文本字段(最多 500 字符),随后是“接受”和“拒绝”两个按钮。
- 接受后 : 绿色“已确认”徽章、屏幕上的确认提示、实用信息提醒,以及组织者为 Business 时的 Google 评价区块。
- 拒绝后 : 红色“已拒绝”徽章,提示组织者已收到通知;只要预约未被取消,受邀人可以重新打开链接并更改回复。
- 已取消的预约 : 回复按钮不再起作用,显示的状态保持为“已取消”。
受邀人输入的自由留言在客户端被截断为 500 字符,在数据库端截断为 1000 字符,随后显示在组织者的预约详情中。
4. 预约状态
| 状态 | 标签 | 触发条件 | 影响 |
|---|---|---|---|
| pending | 待定 | 预约创建时(数据库中的默认值)。 | 链接处于激活状态,受邀人可以回复。计入“待回复数”统计。 |
| accepted | 已确认 | 受邀人点击“接受” — 调用 respond_to_invite(_accept = true)。 | responded_at 和 response_message 被填写;仪表盘上显示绿色徽章。 |
| declined | 已拒绝 | 受邀人点击“拒绝” — respond_to_invite(_accept = false)。 | 记录方式与接受相同,显示红色徽章。该时间段仍保持可见以便跟进。 |
| cancelled | 已取消 | 组织者在其仪表盘上执行的操作。 | 链接不再接受回复(respond_to_invite 仅更新其他状态)。 |
5. 服务器端流程
- 创建 / 修改 / 删除 : 由已认证的客户端执行;数据隔离由数据库访问规则保证。
- sendInviteEmail:受保护的服务器函数。它会按请求者 ID 过滤重新加载预约,若预约不属于该用户则拒绝执行,随后生成邮件内容、调用 Resend,并记录发送时间戳。
- get_invite(_token):具有更高权限的数据库函数,是唯一的公开读取途径。它仅返回显示所需字段,并对非 Business 组织者隐藏品牌相关信息。
- respond_to_invite(_token, _accept, _message):更新状态、留言和回复时间戳。仅作用于与该令牌对应的行,并忽略已取消的预约。
- get_plan_usage():返回套餐、本月用量以及用于配额显示的管理员标志。
6. 授权逻辑
- 组织者 : 仅对自己的联系人和预约拥有完整的读写权限。
- 未登录的受邀人 : 无法直接访问数据表。只能通过令牌读取一个预约并作出回复。令牌为随机生成(36 位十六进制字符),不会授予任何其他访问权限。
- 角色 : 存储在专用数据表中(用户、版主、管理员),并在服务器端进行校验;绝不会从浏览器读取以授予权限。
- 管理员 : 可访问 /admin 区域(所有账户、联系人和预约),并免除 Free-mium 套餐的配额限制。
- 套餐 : 套餐等级的更改无法从客户端强制执行;只有 Stripe 支付流程或管理员才能修改。
- 配额 : 在每次创建前于数据库端强制执行 — 每自然月 10/15(Free-mium)、30/50(Basic)、50/100(Standard)、250/500(Economic)个联系人/预约,Business 套餐及管理员不受限制。
7. 编辑团队检查清单
- 修改邮件文本:调整发送函数的 HTML 模板,而非邀请页面。
- 修改确认屏幕:调整公开邀请页面。
- 任何新变量都必须来自公开读取函数已暴露的字段。
- 切勿在公开页面上显示其他预约或其他联系人的数据。
- 始终测试全部三种状态:待定、已接受、已拒绝。