通过 Gmail API 发送邮件的完整指南

本文档详细介绍了如何通过编程方式调用 Google Gmail API 来发送电子邮件。整个过程不依赖于 SMTP,而是使用现代的 OAuth 2.0 授权流程和 REST API 调用,这使得它可以在任何网络环境(包括限制了 SMTP 端口的云服务器)下工作。

目录

  1. 前置条件
  2. [第一步:启用 API 和创建 OAuth 凭据](#第一步:启用 API 和创建 OAuth 凭据)
  3. [第二步:获取授权 Token](#第二步:获取授权 Token)
  4. 第三步:发送邮件
  5. 附录:常见问题(FAQ)

1. 前置条件

在开始之前,请确保您拥有:

  • 一个 Google 账户 :任何标准的 @gmail.com 账户或 Google Workspace 账户都可以。这个账户将是发送邮件的账户。
  • 一个 Google Cloud Platform (GCP) 项目:这是调用所有 Google API 的管理中心。即使您的代码不运行在 GCP 上,也必须拥有一个 GCP 项目来启用 API 和创建凭据。如果您没有,可以免费创建一个。
  • Python 环境:本地或服务器上需要安装 Python 3。

2. 第一步:启用 API 和创建 OAuth 凭据

此步骤的目标是在您的 GCP 项目中创建一个"应用身份证",即 credentials.json 文件。

  1. 登录 GCP 控制台 :访问 Google Cloud Console 并选择您的项目。
  2. 启用 Gmail API
    • 在顶部的搜索栏中输入 Gmail API
    • 在搜索结果中选择 "Gmail API" 并点击 "启用" 按钮。
  3. 创建 OAuth 客户端 ID
    • 导航到 "API 和服务" > "凭据" 页面。
    • 点击 "+ 创建凭据" 并选择 "OAuth 客户端 ID"
    • 如果系统提示您配置"OAuth 同意屏幕",请按以下步骤操作:
      • 选择 "外部" (External)。
      • 填写必要的应用信息(应用名称、用户支持电子邮件等)。
      • 在"测试用户"步骤中,添加您将要用来发邮件的那个 Gmail 账户
      • 保存并继续。
    • 返回创建凭据页面,在 "应用类型" (Application type) 中选择 "桌面应用" (Desktop app)。
    • 给它一个名称(例如,"Gmail Sender Script")。
    • 点击 "创建"
  4. 下载凭据文件
    • 创建成功后,会弹出一个窗口显示您的客户端 ID 和密钥。请不要复制它们
    • 点击右侧的 "下载 JSON" 按钮。
    • 将下载的文件重命名为 credentials.json 并将其保存在您的项目文件夹中。此文件非常机密,切勿泄露。

3. 第二步:获取授权 Token

此步骤的目标是让您的 Gmail 账户授权给上一步创建的应用,并生成一把"钥匙",即 token.json 文件。这是一个一次性的本地操作。

  1. 创建 get_token.py 脚本

    在与 credentials.json 相同的文件夹中,创建以下 Python 脚本:

    python 复制代码
    # get_token.py
    import os.path
    from google.auth.transport.requests import Request
    from google.oauth2.credentials import Credentials
    from google_auth_oauthlib.flow import InstalledAppFlow
    
    # 定义需要的权限范围,https://mail.google.com/ 是最高权限
    SCOPES = ["https://mail.google.com/"]
    
    def main():
        """
        引导用户完成 OAuth 2.0 授权流程并生成 token.json。
        """
        creds = None
        # 如果 token.json 已存在,则先加载它
        if os.path.exists("token.json"):
            creds = Credentials.from_authorized_user_file("token.json", SCOPES)
        
        # 如果没有有效的凭据,则启动新的授权流程
        if not creds or not creds.valid:
            # 如果 token 已过期且有 refresh_token,则刷新它
            if creds and creds.expired and creds.refresh_token:
                creds.refresh(Request())
            else:
                # 否则,从 credentials.json 启动新的网页授权流程
                flow = InstalledAppFlow.from_client_secrets_file(
                    "credentials.json", SCOPES)
                creds = flow.run_local_server(port=0)
            
            # 将新获取的凭据写入 token.json 文件
            with open("token.json", "w") as token:
                token.write(creds.to_json())
        
        print("token.json 文件已成功生成或刷新。")
    
    if __name__ == "__main__":
        main()
  2. 安装必要的库

    在终端中运行以下命令:

    bash 复制代码
    pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
  3. 运行脚本

    在终端中运行脚本:

    bash 复制代码
    python get_token.py
  4. 在浏览器中完成授权

    • 脚本会自动在您的浏览器中打开一个 Google 登录页面。
    • 登录您希望用来发邮件的 Gmail 账户
    • 您可能会看到一个"此应用未经 Google 验证"的警告。这是正常的,因为这是您自己的应用。请点击 "高级" > "转至...(不安全)"
    • 在下一个页面中,点击 "允许",授予权限。
    • 授权成功后,您会看到 "The authentication flow has completed" 的消息,并且终端会显示 "token.json 文件已成功生成"。

现在,您的项目文件夹中应该有 credentials.jsontoken.json 两个文件了。


4. 第三步:发送邮件

这是最终发送邮件的脚本。我们使用 Python 内置的 urllib 库,因为它在有网络代理的环境下表现更稳定。

  1. 创建 send_email_raw_api.py 脚本

    在同一个文件夹中,创建以下脚本:

    python 复制代码
    # send_email_raw_api.py
    import json
    import base64
    import logging
    import urllib.request
    from email.mime.text import MIMEText
    from urllib.error import URLError, HTTPError
    
    # 配置日志记录
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
    
    def get_access_token(proxy_url=None):
        """使用 refresh_token 获取新的 access_token"""
        logging.info("尝试刷新 access_token...")
        try:
            with open('token.json', 'r') as f:
                token_data = json.load(f)
            
            with open('credentials.json', 'r') as f:
                # 兼容从 GCP 下载的两种 JSON 格式
                creds_data = json.load(f).get('installed') or json.load(f).get('web')
    
            params = {
                'client_id': creds_data['client_id'],
                'client_secret': creds_data['client_secret'],
                'refresh_token': token_data['refresh_token'],
                'grant_type': 'refresh_token'
            }
            
            data = json.dumps(params).encode('utf-8')
            req = urllib.request.Request(
                creds_data['token_uri'], 
                data=data,
                headers={'Content-Type': 'application/json'}
            )
            
            # 配置代理(如果提供)
            if proxy_url:
                proxy_handler = urllib.request.ProxyHandler({'https': proxy_url, 'http': proxy_url})
                opener = urllib.request.build_opener(proxy_handler)
            else:
                opener = urllib.request.build_opener()
            
            with opener.open(req, timeout=30) as response:
                result = json.loads(response.read().decode('utf-8'))
                logging.info("成功刷新 access_token。")
                return result['access_token']
                
        except Exception as e:
            logging.error(f"刷新 access_token 失败: {e}", exc_info=True)
            return None
    
    def send_email_raw(access_token, to, subject, message_text, proxy_url=None):
        """使用原始 HTTP 请求和 access_token 发送邮件"""
        logging.info("使用原始 API 请求发送邮件...")
        try:
            message = MIMEText(message_text, 'plain', 'utf-8')
            message['to'] = to
            message['from'] = 'me'
            message['subject'] = subject
            raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
    
            api_url = 'https://www.googleapis.com/gmail/v1/users/me/messages/send'
            
            headers = {
                'Authorization': f'Bearer {access_token}',
                'Content-Type': 'application/json'
            }
            body = {'raw': raw_message}
            data = json.dumps(body).encode('utf-8')
    
            req = urllib.request.Request(api_url, data=data, headers=headers, method='POST')
    
            # 配置代理(如果提供)
            if proxy_url:
                proxy_handler = urllib.request.ProxyHandler({'https': proxy_url, 'http': proxy_url})
                opener = urllib.request.build_opener(proxy_handler)
            else:
                opener = urllib.request.build_opener()
    
            logging.info(f"向 {api_url} 发送 POST 请求...")
            with opener.open(req, timeout=30) as response:
                response_data = json.loads(response.read().decode('utf-8'))
                logging.info(f"邮件发送成功!Message ID: {response_data.get('id')}")
                return response_data
    
        except (URLError, HTTPError) as e:
            logging.error(f"发送邮件时发生网络错误: {e}", exc_info=True)
            if hasattr(e, 'read'):
                logging.error(f"错误详情: {e.read().decode()}")
        except Exception as e:
            logging.error(f"发送邮件时发生未知错误: {e}", exc_info=True)
        return None
    
    
    if __name__ == '__main__':
        # --- 配置 ---
        RECIPIENT_EMAIL = "收件人邮箱@example.com"
        EMAIL_SUBJECT = "来自应用的 API 调用问候"
        EMAIL_BODY = "这是一封通过纯 Python urllib 库调用 Gmail API 发送的邮件。"
        # 如果需要通过代理发送,请设置代理地址,否则设为 None
        # PROXY_ADDRESS = "http://127.0.0.1:7890" 
        PROXY_ADDRESS = None
        # --- 配置结束 ---
    
        # 1. 获取最新的 access_token
        access_token = get_access_token(proxy_url=PROXY_ADDRESS)
        
        # 2. 如果成功获取,则发送邮件
        if access_token:
            send_email_raw(
                access_token=access_token, 
                to=RECIPIENT_EMAIL, 
                subject=EMAIL_SUBJECT, 
                message_text=EMAIL_BODY,
                proxy_url=PROXY_ADDRESS
            )
  2. 修改配置并运行

    • 打开 send_email_raw_api.py 文件。
    • 在文件底部的 if __name__ == '__main__': 部分,修改 RECIPIENT_EMAIL, EMAIL_SUBJECT, 和 EMAIL_BODY 的值。
    • 如果您需要通过代理发送,请修改 PROXY_ADDRESS
    • 保存文件并在终端运行:
    bash 复制代码
    python send_email_raw_api.py

如果一切顺利,您将在终端看到成功发送的日志,并且收件人会收到邮件。


5. 附录:常见问题(FAQ)

  • Q: 为什么必须要有 GCP 项目?

    • A: GCP 项目是您作为"开发者"的身份标识。所有 Google API 的启用、凭据创建和用量管理都必须依托于一个 GCP 项目。
  • Q: credentials.jsontoken.json 有什么区别?请用通俗的比喻解释。

    • A: 当然,这是一个核心概念。我们可以用一个"机器人管家"的例子来理解:
      • credentials.json 是什么?------ 机器人的"出厂说明书 & 身份证"

        • 这个文件是在您的 GCP 工厂里生成的,它定义了您的机器人管家是谁。
        • 它包含了客户端 ID (相当于机器人的身份证号 ,是公开的)和客户端密钥 (相当于机器人的出厂机密代码,是绝对私密的)。
        • 这份"说明书"是永久有效的,它只代表机器人的身份,但本身没有任何权力。任何不认识这个机器人的家庭都不会让它进门。
      • token.json 是什么?------ 特定家庭的"门禁卡"

        • 这个文件是在您(家庭主人)亲自"面试"过机器人,并同意授权后,才颁发给这个特定机器人的"门禁卡"。
        • 它包含了 access_token(一张临时通行证 ,可能一小时就过期)和 refresh_token(一张长期身份卡)。
        • 当机器人需要进门工作时,它会出示"临时通行证" (access_token)。如果通行证过期了,它就会出示它的"长期身份卡" (refresh_token) 和自己的"身份证" (credentials.json),去物业处(Google)换一张新的"临时通行证"。
      • 它们如何协同工作?

        • 您的代码(机器人)在工作时,必须同时携带 自己的"身份证" (credentials.json) 和您家颁发的"门禁卡" (token.json)。
        • 如果门禁卡里的临时通行证过期了,它就需要用门禁卡里的长期身份卡加上自己身份证上的机密代码,才能换到新的临时通行证。
        • 总结:credentials.json 是"我是谁",token.json 是"我被谁授权可以做什么"。两者缺一不可。
  • Q: 我可以把这两个 JSON 文件提交到 Git 仓库吗?

    • A: 绝对不能! 这两个文件都是高度机密的,泄露它们等同于泄露了您应用乃至用户邮箱的控制权。请务必将它们添加到 .gitignore 文件中。
  • Q: token.json 会过期吗?

    • A: 是的,token.json 里的 access_token 通常只有1小时的有效期。但它包含的 refresh_token 有效期很长。我们提供的脚本会自动使用 refresh_token 去换取新的 access_token,所以您几乎无感。
相关推荐
深圳市恒讯科技14 小时前
防止服务器被黑:终极防范网络攻击指南
运维·服务器·网络安全
橘颂TA14 小时前
【Linux】从 “抢资源” 到 “优雅控场”:Linux 互斥锁的原理与 C++ RAII 封装实战(Ⅰ)
linux·运维·服务器·c++·算法
RisunJan14 小时前
Linux命令-init命令(管理运行级别和控制系统状态)
linux·运维·服务器
ayaya_mana14 小时前
Chrony:通用-替换国内 NTP 源进行时间同步
linux·运维·服务器·chrony
深耕半夜14 小时前
debug函数
linux·运维·服务器
一叶星殇14 小时前
ASP.NET Core 后端如何通过 Nginx 获取真实客户端 IP 完整指南
服务器·tcp/ip·nginx
duration~14 小时前
ARP 协议详情
网络·网络协议·tcp/ip·智能路由器
zbtlink14 小时前
常见的家用路由器耗电量高吗?不同产品耗电量会不会有差别
网络·智能路由器
oMcLin14 小时前
如何在 Red Hat Linux 服务器上使用 Ansible 自动化部署并管理多节点 Hadoop 集群?
linux·服务器·ansible