SMTP 全称为 Simple Mail Transfer Protocol,即简单邮件传输协议,它是一组用于从源地址到目的地址传送邮件的规则,同时会控制信件的中转方式,一般我们发送邮件都是通过这一协议来完成的。
Python 内置的 smtplib 模块对 SMTP 协议进行了简单的封装,借助它我们可以很轻松的实现用代码来发送邮件。
连接 SMTP 服务器 要发送邮件,很明显需要先连接到一个可用的邮件服务器,为此我们需要指定服务器地址和端口。
由于各种历史遗留问题,现在仍在使用的 SMTP 服务端口有三个,分别是:25端口(明文传输)、465端口(SSL 加密)和 587端口(STARTTLS 加密)。不同的端口处理情况稍有不同,下面在代码中分别演示三种端口的连接方式。
1 2 3 4 5 6 7 8 9 10 11 import smtplibsmtp_server = smtplib.SMTP(host="smtp.xxx.xxx" , port=25 ) smtp_server = smtplib.SMTP_SSL(host="smtp.xxx.xxx" , port=465 ) smtp_server = smtplib.SMTP(host="smtp.xxx.xxx" , port=587 ) smtp_server.starttls()
登录 SMTP 服务器 连上服务器之后还需要用我们的邮箱登录才能发送邮件(注意 QQ 邮箱、163 邮箱等使用 SMTP 服务需要的密码是在后台申请的授权码,不是你在网页上登录邮箱时用的密码)。
1 smtp_server.login(user="test@xxx.xxx" , password="test_password" )
构造邮件 电子邮件本质上可以看作一种按特定格式组织的文本文件,除了正文内容之外,标准邮件一般还需要三个头部信息: From(发件人), To(收件人)和 Subject(邮件主题)。所以说发送邮件并不是将你想发的内容传过去就行了,我们还需要先按照一定的规则“构造”一封邮件。
常见的邮件主要有纯文本邮件、HTML 邮件、带附件的邮件几种类型,下面分别演示这三种类型邮件的构造方式。
纯文本邮件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from email.mime.text import MIMETextcontent = "这是一封纯文本邮件" subject = "纯文本邮件测试" from_user = "test@xxx.xxx" to_user = "test2@xxx.xxx" my_mail = MIMEText(content, _subtype="plain" , _charset="utf8" ) my_mail["From" ] = from_user my_mail["To" ] = to_user my_mail["subject" ] = subject
HTML 邮件 构造 HTML 邮件只需要将构造纯文本邮件代码中 MIMEText()
的 _subtype 参数修改为 html 即可,如下:
1 my_mail = MIMEText(content, _subtype="html" , _charset="utf8" )
带附件的邮件 带附件的邮件与上面两种稍有不同,我们需要借助 MIMEMultipart()
构造一封多组件邮件,再将文本内容、附件内容依次添加进去,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from email.mime.application import MIMEApplicationfrom email.mime.multipart import MIMEMultipartfrom email.mime.text import MIMETextcontent = "这是一封带附件的邮件" subject = "带附件的邮件测试" from_user = "test@xxx.xxx" to_user = "test2@xxx.xxx" file_path = "xxx/test.txt" file_name = "test.txt" file_content = open (file_path, "rb" ).read() my_mail = MIMEMultipart() text_msg = MIMEText(content, _subtype="plain" , _charset="utf8" ) my_mail.attach(text_msg) file_msg = MIMEApplication(file_content) file_msg.add_header("content-disposition" , "attachment" , filename=file_name) my_mail.attach(file_msg) my_mail["From" ] = from_user my_mail["To" ] = to_user my_mail["subject" ] = subject
发送邮件 构造好了邮件,连接并登录了 SMTP 服务器,接下来要发送邮件就很简单了,直接调用 send_message()
函数即可。
1 2 3 4 5 6 7 8 9 10 11 smtp_server = ...... my_mail = ...... from_user = "test@xxx.xxx" to_user = "test2@xxx.xxx" smtp_server.send_message(my_mail)
封装邮件发送方法 像上面这样一步步构造邮件、发送邮件,写一次还好,经常需要这样写的话还是有点繁琐的,所以我们来给它稍微封装一下,以后直接调用即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 import osimport smtplibimport emailfrom email.mime.application import MIMEApplicationfrom email.mime.multipart import MIMEMultipartfrom email.mime.text import MIMETextclass MailSender (object ): """ 邮件发送器,封装smtp发送邮件的常用操作 """ def __init__ (self, user: str , password: str , host: str , port: int ): """ 初始化smtp服务器连接 :param user: 邮箱用户,支持 name<prefix@example.com> 的形式,会自动从中提取邮箱地址用于登录 :param password: smtp登录密码 :param host: smtp服务器地址 :param port: smtp服务器端口,仅能使用25、465和587 """ self.__user = user self.__login_mail = email.utils.getaddresses([user])[0 ][1 ] if port == 25 : self.__smtp_server = smtplib.SMTP(host=host, port=port) elif port == 465 : self.__smtp_server = smtplib.SMTP_SSL(host=host, port=port) elif port == 587 : self.__smtp_server = smtplib.SMTP(host=host, port=port) self.__smtp_server.starttls() else : raise ValueError("Can only use port 25, 465 and 587" ) self.__smtp_server.login(user=self.__login_mail, password=password) def send ( self, to_user: str , subject: str = "" , content: str = "" , subtype: str = "plain" ): """ 发送纯文本邮件 :param to_user: 收件人,支持 name<prefix@example.com> 的形式,如需同时发给多人,将多个收件人用半角逗号隔开即可 :param subject: 邮件主题,默认为空字符串 :param content: 邮件正文,默认为空字符串 :param subtype: 邮件文本类型,只能为 plain 或 html,默认为 plain """ self.__check_subtype(subtype) msg = MIMEText(content, _subtype=subtype, _charset="utf-8" ) msg["From" ] = self.__user msg["To" ] = to_user msg["subject" ] = subject self.__smtp_server.send_message(msg) def send_with_attachment ( self, to_user: str , attachment_path: str , attachment_name: str = "" , subject: str = "" , content: str = "" , subtype: str = "plain" , ): """ 发送带附件的邮件 :param to_user: 收件人,支持 name<prefix@example.com> 的形式,如需同时发给多人,将多个收件人用半角逗号隔开即可 :param attachment_path: 附件文件的路径 :param attachment_name: 附件在邮件中显示的名字,设为空字符串时(默认)直接使用文件名 :param subject: 邮件主题,默认为空字符串 :param content: 邮件正文,默认为空字符串 :param subtype: 邮件文本类型,只能为 plain 或 html,默认为 plain """ self.__check_subtype(subtype) with open (attachment_path, "rb" ) as f: file_content = f.read() if attachment_name == "" : attachment_name = os.path.basename(attachment_path) msg = MIMEMultipart() text_msg = MIMEText(content, _subtype=subtype, _charset="utf-8" ) msg.attach(text_msg) file_msg = MIMEApplication(file_content) file_msg.add_header( "content-disposition" , "attachment" , filename=attachment_name ) msg.attach(file_msg) msg["From" ] = self.__user msg["To" ] = to_user msg["subject" ] = subject self.__smtp_server.send_message(msg) def __check_subtype (self, subtype: str ): if subtype not in ("plain" , "html" ): raise ValueError('Error subtype, only "plain" and "html" can be used' ) else : pass
调用时只需要先实例化一个 MailSender
对象,然后就可以使用对应的 send 函数来发送邮件了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if __name__ == "__main__" : test_msg = """ <p>邮件发送测试</p> <p><a href="https://xirikm.net/">这是一个链接</a></p> """ attachment_path = "xxx/xxx.txt" sender = MailSender("test@xxx.xxx" , "test_password" , "smtp.xxx.xxx" , 587 ) sender.send("test2@xxx.xxx" , "纯文本邮件" , test_msg, "plain" ) sender.send("test2@xxx.xxx" , "html邮件" , test_msg, "html" ) sender.send_with_attachment( "test2@xxx.xxx" , attachment_path, "xxx.txt" , "带附件的html邮件" , test_msg, "html" ) sender.send_with_attachment("test2@xxx.xxx" , attachment_path)