Skip to main content

基础

Base URLhttp://kira-notify.internal:8084(内网专用)
鉴权/notify/emails 需 header X-Internal-Key: <INTERNAL_KEY>/health 无需
Content-Typeapplication/json
服务无公网入口。只能从 Fly org 私网(kira-be / kira-queue)或 WireGuard 调用。X-Internal-Key 缺失或不匹配 → 401;服务端未配置 INTERNAL_KEY500

POST /notify

高层语义事件入口(推荐)。声明 type + userId,由 notify 解析用户、判定门槛、渲染并投递。

请求

{
  "type": "daily_digest",
  "userId": "uuid",
  "data": {},
  "contact": { "email": "user@example.com", "locale": "en-US" }
}
字段类型必填说明
typestringnotification type,见 Notification Types
userIdstring用户 UUID,用于从 Supabase 解析联系方式
dataobjecthandler 专属载荷(当前 handler 未使用)
contact{ email?, locale? }用户记录不可用时的兜底(如 account_deletion.deleted,用户已删)。提供 email 时跳过 Supabase 查询
typeuserId400`type` and `userId` are required。未注册的 type400Unknown notification type: <type>

响应:已投递

{
  "delivered": [
    { "channel": "email", "providerId": "<resend-id>" }
  ]
}
deliveredDeliveryResult[]——每个元素含投递渠道与 provider 返回的 ID(email 即 Resend message id)。

响应:已跳过

门槛 / 偏好不满足或用户找不到时,不是错误,返回 200 + skip 标记:
{ "skipped": true, "reason": "disabled_by_user" }
reason触发条件
disabled_by_user用户 is_open_notification = false(关闭了通知)
plan_gate不满足 plan 门槛(如 daily_digest 要求 plan === "free"
user_not_foundSupabase 查不到该用户(且未提供 contact.email 兜底)
skip 在代码里通过 handler 抛 NotificationSkipped(reason) 实现,路由层捕获后转成 { skipped: true, reason }SkipReason union 即上表三值。

示例

curl -X POST http://kira-notify.internal:8084/notify \
  -H "X-Internal-Key: $INTERNAL_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "account_deletion.scheduled", "userId": "<uuid>" }'

POST /emails

低层 Resend 兼容原始邮件发送。用于不值得注册为 notification type 的一次性邮件。不查用户、不做门槛判断、不走 i18n/模板——调用方自备 subject 与正文。

请求

{
  "from": "Kira Art <support@kira.art>",
  "to": "user@example.com",
  "subject": "Hello",
  "html": "<p>Hi</p>"
}
字段类型必填说明
fromstring发件人
tostring | string[]收件人
subjectstring主题
htmlstring⚠️HTML 正文
textstring⚠️纯文本正文
ccstring | string[]抄送
bccstring | string[]密送
reply_tostring | string[]回复地址(映射到 Resend replyTo
from / to / subject 任一缺失 → 400htmltext 至少提供其一,否则 channel 抛 Either \html` or `text` must be provided`。

响应

{ "id": "<resend-id>" }
id 即 Resend 返回的 message id。Resend 返回 error 或无 id 时,channel 抛错 → 500

示例

curl -X POST http://kira-notify.internal:8084/emails \
  -H "X-Internal-Key: $INTERNAL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Kira Art <support@kira.art>",
    "to": "user@example.com",
    "subject": "Hello",
    "html": "<p>Hi</p>"
  }'

GET /health

健康检查,无需鉴权。
{ "status": "ok" }

错误约定

状态场景
200投递成功(delivered)或已跳过(skipped
400缺必填字段 / 未知 notification type
401X-Internal-Key 缺失或不匹配
500INTERNAL_KEY 未配置 / Resend 发送失败 / 其他未捕获异常
onError< 500HTTPException 会清掉 c.error,避免 @hono/otel 把 4xx span 误标 ERROR 污染 Dash0 错误率;只有真 5xxcaptureException 并记 ERROR span。