指南 · Gmail API
产品团队应如何安全地实现 Gmail API?
应将 Gmail API 实现为对某个特定 Gmail 邮箱的委派访问,而不是当作通用的邮件投递凭据。选择能支撑该功能的最小 OAuth 作用域,保护授权状态和刷新令牌,并让每个邮箱都限定在租户范围内。使用成熟的互联网邮件库构建邮件,记录返回的 Gmail 消息 ID,并通过 Pub/Sub 加历史记录同步变更。将服务账号模拟(impersonation)视为由 Workspace 管理员做出的决定。最后,将 API 受理、收件服务器接收和进入收件箱作为不同的结果分开处理。
编写代码之前先确定邮箱模型
Gmail API 操作的是用户的 Gmail 邮箱。当产品必须读取该邮箱、整理其标签和会话、创建草稿、以已授权用户的身份发送邮件或同步邮箱变更时,适合使用它。这种权限比从已验证的产品域名调用应用邮件 API 要大得多。首先明确具体的邮箱工作,以及由谁授予访问权限。面向用户的产品通常对每个连接的 Google 账户使用 OAuth 同意授权。内部的 Google Workspace 自动化则可能改用经管理员批准的全网域授权(domain-wide delegation)。如果唯一的需求是从公司控制的域名发送收据、验证链接、告警或其他由产品触发的邮件,请完全避免访问邮箱,并评估事务性邮件 API。这一架构决策能在任何安全控制或同意界面不得不去弥补之前,就减少不必要的访问权限。
申请实际可行的最小作用域
为正确的应用类型配置 OAuth 客户端,使用精确注册的重定向 URI,并通过不可预测的 state 值将授权响应绑定到发起请求的浏览器会话。在用户启用需要该权限的功能时,结合上下文申请访问权限。对于仅发送的集成,`https://www.googleapis.com/auth/gmail.send` 比读取或修改邮箱的作用域更窄。Google 将 `gmail.send` 归为敏感作用域,而 `gmail.readonly`、`gmail.compose` 和 `gmail.modify` 等作用域属于受限作用域。使用敏感或受限访问权限的公开应用可能需要通过 OAuth 验证,而在服务端存储或传输受限作用域数据,还可能触发额外的安全评估要求。只有在确实需要后台工作时才申请离线访问。加密刷新令牌,将每个令牌关联到一个内部租户和一个 Google 主体,绝不将其暴露给浏览器代码或日志,并提供经过测试的断开连接流程,删除本地凭据并停止后台处理。
理解服务账号和全网域授权
服务账号是一种应用身份,而不是可以直接拿来用的 Gmail 收件箱。它本身并不能访问员工的邮件。对于 Google Workspace 用户数据,必须由超级管理员通过全网域授权,明确为该服务账号的数字客户端 ID 授予一份精确的 OAuth 作用域列表。之后,应用为指定用户请求委派凭据,每次 API 调用都在已授权的作用域内以该用户的权限执行。在任务数据和审计日志中明确记录被模拟的主体,避免后台 worker 悄悄切换邮箱。对性质明显不同的工作负载使用不同的服务账号;如果运行环境可以使用托管凭据,就避免使用可下载的私钥;并定期审查全网域授权。个人 Gmail 账户没有能够授予这种组织范围委派的 Workspace 管理员,因此对这类账户请使用用户 OAuth 同意授权。
发送邮件时不失去控制和可审计性
Gmail 通过 `users.messages.send` 接收放在 `raw` 字段中、经 base64url 编码的完整互联网邮件;产品也可以先创建草稿,之后再发送。请使用持续维护的邮件库来生成 From、To、Cc、Bcc、Subject、Date、Message-ID、纯文本、HTML 和附件结构,而不是手动拼接邮件头行。在编码之前校验收件人和内容,拒绝邮件头注入,并设置明确的大小限制。在调用 Gmail 之前让产品操作具备幂等性:持久化一个稳定的应用事件键、目标邮箱主体和发送尝试状态。收到成功响应后,将 Gmail 返回的消息 ID 和会话 ID 与该事件一起保存。如果客户端在请求发出后超时,请先对账邮箱状态再重试,因为邮件可能已经被受理。即使原始响应丢失,盲目重试也可能导致重复发送。当内容或收件人需要审批时,请先创建草稿并交由人工审核。
通过历史记录同步邮箱变更
对于服务端的邮箱集成,Gmail watch 会通过 Google Cloud Pub/Sub 发布变更信号。通知只是提示您进行同步,并不包含完整的邮件内容。持久化保存 watch 响应中当前的历史 ID 和过期时间,快速确认通知,并从上一次成功提交的历史 ID 开始调用 `users.history.list`,以发现邮件和标签的变更。只获取功能所需的邮件,并在本地写入成功后再推进检查点。通知可能延迟或重复,因此邮件和历史记录的处理必须是幂等的。Gmail 要求至少每七天续订一次邮箱 watch,并建议每天续订;请在过期之前留足余量安排续订,并在失败时发出告警。如果保存的历史 ID 超出了 Gmail 的可用范围,API 会返回 HTTP 404。请将其作为既定的恢复路径处理:执行一次受控的完整同步,建立新的检查点,然后恢复增量处理,而不是无休止地用无效的历史 ID 重试。
采用分阶段的实施和验证流程
第一,记录该功能是发送、读取、修改还是监听邮件,并将每项操作映射到其最小 OAuth 作用域。第二,为开发和生产环境分别创建独立的 Google Cloud 项目或 OAuth 客户端,配置精确的重定向 URI,并指定具名的凭据负责人。第三,实现授权:校验 state,仅在需要时申请离线访问,加密存储令牌,支持令牌吊销,并进行租户级别的访问检查。第四,使用受控邮箱进行测试:连接、刷新已过期的访问令牌、撤销同意、重新连接、发送一次、模拟一次结果不确定的超时,并确认能防止重复发送。第五,如果需要接收变更,配置 Pub/Sub 权限,启动 watch,增量处理历史记录,强制触发一次检查点过期的恢复,并验证 watch 续订。第六,添加按用户划分的工作队列、有上限的指数退避、结构化的错误分类,以及默认不记录邮件正文和令牌的审计日志。上线之前,完成所有必需的 Google 验证和安全审查,发布准确的数据使用说明,并演练凭据轮换和用户数据删除。
为配额、重试和部分失败做好规划
Gmail 以配额单位而非仅以请求数量衡量 API 使用量。Google 的配额页面列出每个项目每分钟 1,200,000 个单位,以及每个项目中每位用户每分钟 6,000 个单位。它将 `messages.send`、`drafts.send` 和 `watch` 各列为 100 个单位,并规定每封邮件最多 500 位收件人。Gmail 独立的用户发送上限仍适用于 API、Web 和 SMTP 客户端。请将 Cloud 控制台和当前文档视为运行时配置输入,而不要将已发布的限制硬编码到业务逻辑中。按邮箱串行化或公平排队工作,限制并发数,并且仅对暂时性响应采用带抖动的指数退避和有限截止时间进行重试。不要将授权、策略、无效收件人或格式错误邮件的错误当作容量问题重试。multipart 批处理可减少连接开销,但每个内部调用仍会消耗配额,且可能独立失败。
将受理、投递和进入收件箱区分开
`messages.send` 调用成功,意味着 Gmail 受理了经过授权的 API 请求并返回了一个 Gmail Message 资源。它并不能证明每个收件人的邮件服务器都接收了该邮件,也无法确定收件系统把邮件归到了哪里。收件服务器投递意味着目标系统承担了 SMTP 投递责任。进入收件箱则是之后的过滤结果,例如主收件箱、推广、隔离或垃圾邮件。因此,当产品需要事务性邮件的投递、退信或投诉遥测数据时,Gmail 的邮箱 API 并不能替代服务商的事件流。保留 Gmail 消息 ID 用于对账,但除非有其他证据支持已送达,否则向用户展示的状态应准确地描述为“已发送”或“已被 Gmail 受理”。身份验证、预期的收件人、内容质量、发送行为和目标方策略都会影响后续处理。API 响应无法确定或保证邮件最终落在收件人的哪个文件夹。
了解事务性邮件 API 适合的是另一类工作
当产品需要获授权访问个人或组织的 Gmail 邮箱时,请使用 Gmail API,包括访问会话、标签、草稿或邮箱同步。事务性邮件 API 适用于另一种架构:从组织控制的域名发送由应用触发的邮件,无需获授权读取用户的 Gmail 邮箱。边界明确时,产品可以同时使用两类系统,例如用 Gmail OAuth 读取支持智能体连接的邮箱,再用单独验证的事务性服务商发送产品收据。请将凭据、同意、邮件存储、重试策略和审计记录分离,避免邮箱权限泄漏到全应用发送,且事务性凭据无法读取用户的 Gmail。
常见问题
服务账号可以访问任意 Gmail 邮箱吗?
不可以。服务账号不会自动获得 Gmail 用户数据的访问权限。必须由 Google Workspace 超级管理员为其数字客户端 ID 和经批准的作用域授予全网域授权,之后应用才能明确地模拟该组织中的某个用户。对于个人 Gmail 账户,请改用用户 OAuth 同意授权。
仅发送的 Gmail 集成应申请哪个 OAuth 作用域?
先评估 `https://www.googleapis.com/auth/gmail.send`,它允许代表用户发送邮件,但不授予一般性的邮箱读取权限。在申请更宽泛的作用域之前,确认确实没有任何产品需求要用到草稿、读取邮件、标签或修改操作,并考虑 Google 对敏感作用域的验证规则。
Gmail API 发送成功是否意味着邮件已送达?
不是。它只确认 Gmail 受理了经过授权的 API 操作并返回了一条邮件记录。收件服务器接收和进入收件箱是之后各自独立的状态。除非有其他可信的信号支持这一结论,否则不要将邮件标记为已送达,也不要承诺邮件会进入收件箱。
Gmail 推送通知包含完整的新邮件吗?
不包含。Pub/Sub 通知表示邮箱状态发生了变化,并附带用于继续同步的信息。应用应从已保存的历史 ID 开始查询 Gmail 历史记录,获取所需的邮件数据,以幂等方式处理,然后推进检查点。
Gmail 邮箱 watch 需要多久续订一次?
Google 要求至少每七天调用一次 `watch`,并建议每天续订。保存返回的过期时间,在到期前续订,监控失败情况,并保留一个兜底的同步任务,以免错过续订后悄无声息地产生无限扩大的数据缺口。
团队什么时候应该使用事务性邮件 API 而不是 Gmail API?
如果需求是从组织控制的域名发送由应用触发的邮件,并且没有任何功能需要访问某个人的 Gmail 邮箱,请使用事务性邮件 API。如果产品确实需要委派访问邮箱中的邮件、会话、标签、草稿、设置或“以他人身份发送”(send-as)权限,请使用 Gmail API。
参考来源
- Gmail API 概览 — Google for Developers
- 选择 Gmail API 作用域 — Google for Developers
- 实现服务端授权 — Google for Developers
- 为 Web 服务器应用使用 OAuth 2.0 — Google for Developers
- 为服务器到服务器应用使用 OAuth 2.0 — Google for Developers
- 创建和发送电子邮件 — Google for Developers
- 在 Gmail API 中配置推送通知 — Google for Developers
- 将客户端与 Gmail 同步 — Google for Developers
- Gmail API 使用限制 — Google for Developers
- 解决 Gmail API 错误 — Google for Developers
- Google Workspace API 用户数据与开发者政策 — Google for Developers
- RFC 5322:互联网邮件格式 — RFC Editor
- RFC 5321:简单邮件传输协议(SMTP) — RFC Editor