--- name: tencent-ses-service version: 1.1.0 description: | 腾讯云官方的邮件推送 SES 服务使用 skill。 涵盖:SMTP 发送邮件所需的全部环境变量、API 接口设计规范、验证码邮件的完整发送流程(含人机验证、频率控制、Redis 存储)。 本 skill 以网络协议 / HTTP API 接口级别描述,不绑定具体编程语言。 model: deepseek-chat tags: - SES - SMTP - 腾讯云 - 邮件推送 - 验证码 - Redis rootUrl: https://raw.githubusercontent.com/LSTM-Kirigaya/jinhui-skills/main/skills/tencent-ses-service/SKILL.md allowed-tools: - Read - Write - Edit - Bash - Grep - Glob --- # 腾讯云邮件推送 SES 服务 本 skill 描述如何通过腾讯云 SES(Simple Email Service)的 **SMTP 协议** 发送邮件。腾讯云 SES 提供两种接入方式:**API SDK 方式**(`SendEmail` 接口)和 **SMTP 方式**。本文档基于 SMTP 方式,因为它不依赖腾讯云 SDK,通用性更强。 --- ## 一、前置条件 ### 1.1 开通腾讯云 SES 服务 在腾讯云控制台开通 SES 服务,完成发件域名配置和 DKIM/SPF 验证。 - 控制台地址:`https://console.cloud.tencentcloud.com/ses` - 发件域名需要已备案且通过 DNS 验证 - 配置完成后获取 SMTP 凭据(见下方环境变量) ### 1.2 依赖的外部服务 | 服务 | 用途 | |---|---| | Redis | 存储验证码(含过期时间)、滑块验证 token、频率控制标记 | | 滑块验证服务 | 生成并校验人机验证 token(本项目使用腾讯云验证码服务) | --- ## 二、环境变量 以下环境变量为必填项,缺一不可: | 变量名 | 示例值 | 说明 | |---|---|---| | `TENCENTCLOUD_SMTP_HOST` | `smtp.qcloudmail.com` | 腾讯云 SES SMTP 服务器地址 | | `TENCENTCLOUD_SMTP_PORT` | `465` | SMTP 端口。`465` 为 SSL 直连,`587` 为 STARTTLS | | `TENCENTCLOUD_SMTP_USER` | `noreply@healthymax.cn` | SMTP 认证用户名(即发件邮箱地址) | | `TENCENTCLOUD_SMTP_PASSWORD` | `WDWOPKOPkop12312dko` | SMTP 认证密码(在腾讯云 SES 控制台生成,非邮箱登录密码) | ### SMTP 密码获取方式 1. 登录腾讯云 SES 控制台 2. 进入「发件域名」→ 选择已配置的域名 3. 点击「SMTP 密码」→「生成密码」 4. 记录生成的密码(只显示一次) --- ## 三、API 接口设计 ### 3.1 发送验证码邮件 **端点:** `POST /api/ses/send-verification-code` **认证要求:** 无需登录态(公开接口),但需要滑块验证 token。 #### 请求体(JSON) ``` 字段 类型 必填 说明 ──────────────────────────────────────────────── email string 是 收件人邮箱地址 captchaToken string 是 滑块验证通过后获得的 token ``` 请求示例(仅供参考,非特定语言): ```json { "email": "user@example.com", "captchaToken": "abc123def456..." } ``` #### 响应体(JSON) 成功时 HTTP 200: ```json { "success": true, "message": "验证码发送成功", "data": { "requestId": "" } } ``` 失败时返回对应 HTTP 状态码(400/429/500): ```json { "success": false, "message": "错误描述", "data": null } ``` #### 错误码一览 | HTTP 状态码 | message | 触发条件 | |---|---|---| | 400 | `邮箱地址不能为空` | `email` 字段缺失或为空 | | 400 | `邮箱格式不正确` | `email` 不符合正则 `^[^\s@]+@[^\s@]+\.[^\s@]+$` | | 400 | `请先完成滑块验证` | `captchaToken` 未传 | | 400 | `验证已过期,请重新完成滑块验证` | captcha token 在 Redis 中不存在或已过期 | | 429 | `发送验证码过于频繁,请稍后再试` | 同一邮箱或同一 IP 在 30 秒内重复请求 | | 500 | `验证服务不可用` | Redis 连接不可用 | | 500 | `发送邮箱验证码异常: ...` | SMTP 发送失败(携带具体错误信息) | --- ## 四、发送流程(网络接口级描述) ### 整体时序 ``` 客户端 服务端 Redis SMTP 服务器 │ │ │ │ │ POST /api/ses/send- │ │ │ │ verification-code │ │ │ │ {email, captchaToken} │ │ │ │ ────────────────────────────▶ │ │ │ │ │ GET captcha:token:{token} │ │ │ │ ──────────────────────────────▶ │ │ │ │ ◀────────────────────────────── │ │ │ │ DEL captcha:token:{token} │ │ │ │ ──────────────────────────────▶ │ │ │ │ │ │ │ │ GET throttle:email:{email} │ │ │ │ ──────────────────────────────▶ │ │ │ │ GET throttle:email:ip:{ip} │ │ │ │ ──────────────────────────────▶ │ │ │ │ │ │ │ │ SETEX email_verification_code: │ │ │ │ {email} 900 {code} │ │ │ │ ──────────────────────────────▶ │ │ │ │ SETEX throttle:email:{email} │ │ │ │ 30 {timestamp} │ │ │ │ ──────────────────────────────▶ │ │ │ │ SETEX throttle:email:ip:{ip} │ │ │ │ 30 {timestamp} │ │ │ │ ──────────────────────────────▶ │ │ │ │ │ │ │ │ EHLO / STARTTLS / AUTH LOGIN │ │ │ │ ─────────────────────────────────────────────────▶ │ │ │ MAIL FROM / RCPT TO / DATA │ │ │ │ ─────────────────────────────────────────────────▶ │ │ │ ◀───────────────────────────────────────────────── │ │ │ │ │ │ ◀────────────────────────────│ │ │ │ {success: true, │ │ │ │ data: {requestId: "..."}} │ │ │ ``` ### 步骤详解 #### Step 1:参数校验 - 检查 `email` 非空 - 检查 `email` 匹配格式正则 `^[^\s@]+@[^\s@]+\.[^\s@]+$` - 检查 `captchaToken` 非空 - 检查 Redis 连接可用 #### Step 2:滑块验证 - 从 Redis 读取 key:`captcha:token:{captchaToken}` - 读取后立即删除该 key(一次性使用,防重放) - 若 key 不存在或已过期 → 返回 400,要求用户重新完成滑块验证 #### Step 3:频率控制(双重节流) 检查两个 Redis key,任一存在即返回 429: | Redis Key | TTL | 含义 | |---|---|---| | `throttle:email:{email}` | 30s | 同一邮箱 30 秒内只能发送一次 | | `throttle:email:ip:{clientIP}` | 30s | 同一 IP 30 秒内只能发送一次 | > 客户端 IP 从请求头 `X-Forwarded-For` 的第一个值获取,无代理时回退为 `127.0.0.1`。 #### Step 4:生成验证码并存入 Redis - 生成 6 位随机数字验证码(范围 100000-999999) - 存入 Redis:`SETEX email_verification_code:{email} 900 {code}` - TTL = 900 秒(15 分钟) - 设置节流标记: - `SETEX throttle:email:{email} 30 {timestamp}` - `SETEX throttle:email:ip:{ip} 30 {timestamp}` #### Step 5:通过 SMTP 发送邮件 连接到腾讯云 SMTP 服务器并发送邮件。SMTP 会话参数: | 参数 | 值 | 说明 | |---|---|---| | Host | `{TENCENTCLOUD_SMTP_HOST}` | 腾讯云 SMTP 服务器 | | Port | `{TENCENTCLOUD_SMTP_PORT}` | `465`(SSL)或 `587`(STARTTLS) | | Secure | `true`(465 端口)/ `false`(587 端口) | 是否使用 SSL 直连 | | Auth User | `{TENCENTCLOUD_SMTP_USER}` | SMTP 认证用户名 | | Auth Pass | `{TENCENTCLOUD_SMTP_PASSWORD}` | SMTP 认证密码 | 邮件信封(SMTP `MAIL FROM` / `RCPT TO`): | 字段 | 值 | |---|---| | MAIL FROM | `noreply@{发件域名}` 实际地址为 `{TENCENTCLOUD_SMTP_USER}` | | RCPT TO | 请求参数中的 `email` | | From (header) | `"noreply@{域名}" <{TENCENTCLOUD_SMTP_USER}>` | | To (header) | 请求参数中的 `email` | | Subject | `邮箱验证 - 智健启能` | #### Step 6:返回结果 - 成功:返回 HTTP 200,`data.requestId` 为 SMTP 服务器返回的 `messageId` - 失败:返回 HTTP 500,`message` 包含 SMTP 错误详情 #### Step 7:客户端校验验证码 客户端后续可通过独立的验证接口校验验证码(读取 Redis key `email_verification_code:{email}` 并比对),验证通过后删除该 key。 --- ## 五、邮件内容规范 ### HTML 邮件结构 邮件正文使用 HTML 格式,建议遵循以下规范: ``` 结构层次: ├── 外层容器(最大宽度 600px,圆角 24px,白色背景) │ ├── Header:Logo 图片 + 产品名称 │ ├── Divider:1px 分割线 │ ├── Body: │ │ ├── 标题(如"安全验证") │ │ ├── 说明文字 │ │ ├── 验证码展示区(灰底圆角卡片,等宽字体居中展示 6 位数字) │ │ └── 过期提示 │ └── Footer:安全提示 + 版权信息 ``` ### 设计约束 - 移动端适配:使用 `@media only screen and (max-width: 600px)` 覆盖容器宽度和圆角 - 字体栈:`-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif` - 验证码字体:等宽字体(`Monaco, Consolas, monospace`),字间距 `0.2em` - 品牌色:主色 `#f6993b`(橙),文字色 `#1E293B` / `#64748B`,背景 `#F8FAFC` ### HTML 邮件模板示例 > ⚠️ 以下为 HTML 结构说明,具体内容请根据项目品牌调整。代码仅为示意,不绑定任何编程语言。 ```html
...
...
...
``` ### 模板命名与链接规范 #### 模板命名要求 模板名称必须清晰表明邮件的具体用途,**禁止使用模糊不清、无法判断用途的命名**。 | 合法性 | 示例 | 说明 | |---|---|---| | ✅ 合法 | `注册验证码邮件` | 明确表明用途为「注册」场景的验证码邮件 | | ✅ 合法 | `密码重置确认邮件` | 明确表明用途为「密码重置」场景 | | ❌ 非法 | `邮件发送` | 过于笼统,无法判断具体用途 | | ❌ 非法 | `通知邮件` | 未说明通知的具体类型和场景 | > 平台审核时会检查模板名称的明确性,命名不清将直接导致审核驳回。 #### 链接填写要求 HTML 模板中的链接**必须包含完整域名**,仅允许对路径或参数部分使用模板变量。**禁止出现将完整 URL(含协议和域名)整体替换为变量的情况**。 | 合法性 | 示例 | 说明 | |---|---|---| | ✅ 合法 | `https://kirigaya.cn/blog/article?seq={{id}}` | 域名完整,仅参数部分为变量 | | ✅ 合法 | `https://healthymax.cn/verify?token={{token}}` | 域名完整,仅查询参数为变量 | | ❌ 非法 | `{{url}}` | 完整 URL 被变量替代,无法审核域名归属 | | ❌ 非法 | `{{baseUrl}}/path` | 协议和域名部分被变量替代 | > 此要求用于方便平台审核链接指向的域名归属及内容合规性。请在提交审核前完成自检。 --- ## 六、Redis Key 设计汇总 | Key Pattern | 类型 | TTL | 用途 | |---|---|---|---| | `captcha:token:{token}` | 任意 | 由滑块服务设定 | 人机验证 token(验证后删除) | | `throttle:email:{email}` | String (timestamp) | 30s | 邮箱维度的发送频率控制 | | `throttle:email:ip:{ip}` | String (timestamp) | 30s | IP 维度的发送频率控制 | | `email_verification_code:{email}` | String (6位数字) | 900s (15min) | 邮箱验证码存储 | --- ## 七、前端调用约定 ### 触发验证码发送的前置条件 客户端在调用 `POST /api/ses/send-verification-code` 前,必须: 1. 用户已完成滑块验证 → 获得 `captchaToken` 2. 用户输入的邮箱格式已通过前端初步校验 ### 调用模式 ``` 前端组件 sesService API Route │ │ │ │ sendVerificationCode() │ │ │ ─────────────────────────▶ │ │ │ │ POST /api/ses/ │ │ │ send-verification-code │ │ │ ───────────────────────────▶ │ │ │ ◀─────────────────────────── │ │ ◀───────────────────────── │ │ ``` > 示例代码(仅为说明调用模式,不涉及具体语言): > ``` > service.sendVerificationCode({ email: "user@example.com", captchaToken: "xxx" }) > ``` ### 消费者场景 验证码邮件在以下场景被触发: | 场景 | 说明 | |---|---| | 用户注册 | 输入邮箱后发送验证码 | | 邮箱登录 | 选择邮箱验证码登录模式 | | 修改密码 | 向已绑定的邮箱发送验证码 | | 绑定/更换邮箱 | 向新邮箱发送验证码以确认所有权 | --- ## 八、常见问题 ### Q: 为什么选择 SMTP 方式而非 API SDK? SMTP 方式不依赖 `tencentcloud-sdk-nodejs`,使用标准的 SMTP 协议(RFC 5321),可以对接任何支持 SMTP 的邮件库。腾讯云 SES API SDK(`SendEmail` 接口)需要 `SecretId`/`SecretKey` 签名认证,而 SMTP 方式只需用户名密码,部署更简单。 ### Q: 端口 465 vs 587 如何选择? - **465(SSL)**:SMTP over SSL,连接即加密,推荐使用 - **587(STARTTLS)**:先明文连接再升级 TLS,部分云环境防火墙兼容性更好 本 skill 使用 465 端口 + `secure: true`。 ### Q: SMTP 密码和腾讯云 API SecretKey 是同一个吗? **不是。** SMTP 密码是在 SES 控制台单独生成的,仅用于 SMTP 认证。API SecretKey 用于调用腾讯云其他 API(如 SMS 短信服务),两者独立。 ### Q: 如何扩展为发送其他类型的邮件? 在现有 SMTP 连接基础上,只需构造不同的 `Subject` 和 `html` 内容即可发送任意事务邮件。注意: - 发件地址必须使用已配置的发件域名 - 腾讯云 SES 对发送频率和内容有审核要求 - 建议为不同类型的邮件也加入 Redis 节流控制 --- ## 九、安全检查清单 - [ ] SMTP 凭据(`TENCENTCLOUD_SMTP_PASSWORD`)仅存储在服务端环境变量,不暴露给前端 - [ ] 发送验证码前必须校验滑块验证 token - [ ] 验证码存储使用 Redis TTL 自动过期 - [ ] 双重频率控制(邮箱 + IP),防止短信轰炸 - [ ] 滑块验证 token 一次性使用(读后即删),防重放攻击 - [ ] 接口路径加入公开白名单(如反向代理配置)以允许未登录用户访问 - [ ] 邮件内容中的链接使用 HTTPS - [ ] 邮件模板名称清晰明确,能够直接判断邮件用途(如「注册验证码邮件」合法,「邮件发送」非法) - [ ] 邮件模板中的链接包含完整域名,未将协议+域名部分整体替换为变量(如 `{{url}}` 非法,`https://example.com/path?id={{id}}` 合法)