OAuth2.0或应用专用密码来发送Gmail邮件。
一、OAuth2.0方式
1.创建Project: 在Google Dashboard创建一个名为Email的Project
2.启用API: 在API Library中搜索并启用Gmail API
3.创建OAuth同意屏幕: 在OAuth consent screen页面创建同意屏幕
注: 开启后初始状态为Testing,测试完毕后需要点击PUBLISH APP来发布。由于Gmail属于用户敏感信息,必须向Google提交申请才能用于生产环境。如果仅仅是个人使用,保持Testing状态即可。
4.创建OAuth2.0: 在Credentials页面新建一个OAuth 2.0 Client ID
点击DOWNLOAD JSON将client secret下载到本地,此时假设JSON将client secret的路径是/path/to/client_secret.json。
5.配置环境: 基于Python3.8创建一个虚拟环境并安装以下依赖
pip install google-api-python-client==2.116.0
pip install google-auth-oauthlib==1.2.0
6.编写脚本: 使用client secret路径/path/to/client_secret.json来初始化GoogleEmail类即可
# -*- coding: utf-8 -*-
import os
import sys
import base64
import logging
import traceback
from email import encoders
from types import TracebackType
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from googleapiclient.discovery import build
from email.mime.multipart import MIMEMultipart
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
logging.basicConfig(level=logging.INFO)
class GoogleEmail:
def __init__(self, client_secret_path: str = "") -> None:
"""
Initialize an instance of the GoogleEmail class.
Args:
client_secret_path (str): The path of the client secret json file. Defaults to "".
Returns:
None
"""
self._client_secret_path = client_secret_path
self._gmail_service = None
self._init()
def _init(self) -> None:
"""
Initialize the gmail_service.
Returns:
None
"""
tmp_dir = os.path.join(os.path.dirname(__file__), "tmp")
os.makedirs(tmp_dir, exist_ok=True)
google_token_path = os.path.abspath(os.path.join(tmp_dir, "google_email_token.json"))
credentials = None
if os.path.exists(google_token_path):
try:
credentials = Credentials.from_authorized_user_file(
filename=google_token_path,
scopes=["https://www.googleapis.com/auth/gmail.send"]
)
except Exception as e:
logging.error(f"{e}\n{traceback.format_exc()}")
sys.exit(1)
if not credentials or not credentials.valid:
if credentials and credentials.expired and credentials.refresh_token:
credentials.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
client_secrets_file=self._client_secret_path,
scopes=["https://www.googleapis.com/auth/gmail.send"]
)
try:
credentials = flow.run_local_server(port=0)
except Exception as e:
logging.error(f"{e}\n{traceback.format_exc()}")
sys.exit(1)
with open(google_token_path, "w") as f:
f.write(credentials.to_json())
self._gmail_service = build("gmail", "v1", credentials=credentials)
def send(self,
subject: str,
body: str,
to_recipients: str,
cc_recipients: str = None,
bcc_recipients: str = None,
attachment_path: str = None
) -> None:
"""
Send an email using Gmail API.
Args:
subject (str): The email subject.
body (str): The email body.
to_recipients (str): Comma-separated email addresses of the primary recipients.
cc_recipients (str): Comma-separated email addresses of the CC recipients. Default is None.
bcc_recipients (str): Comma-separated email addresses of the BCC recipients. Default is None.
attachment_path (str): Path to the file to be attached. Default is None (no attachment).
Returns:
None
"""
message = MIMEMultipart()
message["subject"] = subject
message.attach(MIMEText(body, "plain"))
message["to"] = to_recipients
if cc_recipients:
message["cc"] = cc_recipients
if bcc_recipients:
message["bcc"] = bcc_recipients
if attachment_path:
attachment_name = os.path.basename(attachment_path)
attachment = MIMEBase("application", "octet-stream")
with open(attachment_path, "rb") as f:
attachment.set_payload(f.read())
encoders.encode_base64(attachment)
attachment.add_header("Content-Disposition", f"attachment; filename={attachment_name}")
message.attach(attachment)
raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode("utf-8")
try:
response = self._gmail_service.users().messages().send(userId="me", body={"raw": raw_message}).execute()
except Exception as e:
logging.error(f"{e}\n{traceback.format_exc()}")
else:
logging.info(f"send email success, response: {response}")
7.发送邮件: 在发送邮件之前需要用户同意Google授权
if __name__ == "__main__":
# OAuth consent screen is required before using Gmail API
GoogleEmail(client_secret_path="/path/to/client_secret.json").send(to_recipients="user@gmail.com", subject="hi", body="hello")
二、应用专用密码方式
1.开启两步验证: 在Security页面开启两步验证
2.创建应用专用密码: 在应用专用密码页面输入名称即可创建
3.编写脚本: 不需要配置环境,直接使用Python内置的标准库即可
# -*- coding: utf-8 -*-
import logging
import smtplib
import traceback
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
logging.basicConfig(level=logging.INFO)
class EmailNotification:
def __init__(self, sender: str, password: str, server: str, recipients: str) -> None:
"""
Initialize the class.
Args:
sender (str): The email address of the sender.
password (str): The password of the email account of sender.
server (str): The SMTP server address.
recipients (str): Comma-separated list of email addresses of the recipients.
Returns:
None
"""
self._sender = sender
self._password = password
self._server = server
self._recipients = recipients
def send(self, subject: str, body: str) -> None:
"""
Send an email using Gmail API.
Args:
subject (str): The email subject.
body (str): The email body.
Returns:
None
"""
msg = MIMEMultipart()
msg["Subject"] = subject
msg["from"] = self._sender
msg["to"] = self._recipients
msg.attach(MIMEText(body, "plain", _charset="utf-8"))
smtp= None
try:
smtp= smtplib.SMTP(self._server, 587)
smtp.starttls()
smtp.login(self._sender, self._password)
smtp.send_message(msg)
except Exception as e:
logging.error(f"{e}\n{traceback.format_exc()}")
else:
logging.info("send email success")
finally:
if smtp:
smtp.quit()
4.发送邮件: 将其中的password更换成应用专用密码(需要去掉其中的空格)
if __name__ == '__main__':
conf = {"sender": "sender@gmail.com",
"password": "password",
"server": "smtp.gmail.com",
"recipients": "recipient@gmail.com"}
EmailNotification(**conf).send(subject="hi", body="hello")