本篇博文是一个我之前没有想要去关注,但就是这样看似轻松的事情,却出现了很多的bug,我尝试用了163,、QQ以及谷歌邮箱去发送邮件,中间遇到了很多波折,其中一根谷歌的小号邮箱也被封了,所以想在这里总结下我的一些错误与笔记。
对于Python来说,需要编写脚本调用邮件服务器来发送邮件,使用的协议是SMTP。接收邮件,使用的协议是POP3和IMAP。这三种的区别与用处后面再提,其实在上述三种邮箱的使用中,差别并不是很大,比如说QQ邮箱的STMP / IMAP是共用一个授权码,而谷歌邮箱则是按照端口划分。
在python中,提供收发邮件的库为smtplib、poplib和imaplib,而我们常用的为smtplib,下面为stmplib的一些常规方法介绍:
方法 | 描述 |
---|---|
SMTP.set_debuglevel(level) | 设置输出debug调试信息,默认不输出 |
SMTP.docmd(cmd[, argstring]) | 发送一个命令到SMTP服务器 |
SMTP.connect([host[, port]]) | 连接到指定的SMTP服务器 |
SMTP.helo([hostname]) | 使用helo指令向SMTP服务器确认你的身份 |
SMTP.ehlo(hostname) | 使用ehlo指令像ESMTP(SMTP扩展)确认你的身份 |
SMTP.ehlo_or_helo_if_needed() | 如果在以前的会话连接中没有提供ehlo或者helo指令,这个方法会调用ehlo()或helo() |
SMTP.has_extn(name) | 判断指定名称是否在SMTP服务器上 |
SMTP.verify(address) | 判断邮件地址是否在SMTP服务器上 |
SMTP.starttls([keyfile[, certfile]]) | 使SMTP连接运行在TLS模式,所有的SMTP指令都会被加密 |
SMTP.login(user, password) | 登录SMTP服务器 |
SMTP.sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[]) | 发送邮件 from_addr:邮件发件人 to_addrs:邮件收件人 msg:发送消息 |
SMTP.quit() | 关闭SMTP会话 |
SMTP.close() | 关闭SMTP服务器连接 |
以上表格转自https://blog.51cto.com/lizhenliang/1875330,那么我们就可以写一个范例如下:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
my_sender='[email protected]' # 发件人邮箱账号
my_pass = 'xxxxxxxxxx' # 发件人邮箱密码
my_user='[email protected]' # 收件人邮箱账号,我这边发送给自己
def mail():
ret=True
try:
msg=MIMEText('successful!','plain','utf-8')
msg['From']=formataddr(["FromRunoob",my_sender]) # 括号里的对应发件人邮箱昵称、发件人邮箱账号
msg['To']=formataddr(["FK",my_user]) # 括号里的对应收件人邮箱昵称、收件人邮箱账号
msg['Subject']="放假通知" # 邮件的主题,也可以说是标题
server=smtplib.SMTP_SSL("smtp.qq.com", 465) # 发件人邮箱中的SMTP服务器,端口是25
server.login(my_sender, my_pass) # 括号中对应的是发件人邮箱账号、邮箱密码
server.sendmail(my_sender,[my_user,],msg.as_string()) # 括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件
server.quit() # 关闭连接
except Exception: # 如果 try 中的语句没有执行,则会执行下面的 ret=False
ret=False
return ret
ret=mail()
if ret:
print("邮件发送成功")
else:
print("邮件发送失败")
将其保存为py文件,然后运行就能去邮箱内查看到当前邮件了,但自己发给自己并不能说明是遵循STMP协议才发送或收到,所以我们需要将其放入一个固定任务中,将收件人当成我们要传的参数,然后检测是否能收到邮件才算是验证成功,这里的这种定时任务,可以简单的使用django和celery达到想要的效果,并且django中也有提供内置的mail模块,但没有stmplib好用,感觉bug有点多省略很多东西,所以还是以stmplib来构造函数:
# settings.py文件中:
EMAIL_USE_TLS = False
EMAIL_USE_SSL = False
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.163.com'
#MAIL_USE_DEBUG = True
#MAIL_USE_TLS = True
# EMAIL_HOST = 'smtp.maxhichina.com'
EMAIL_PORT = 25
# 发送邮件的邮箱
EMAIL_HOST_USER = '[email protected]'
# 在邮箱中设置的客户端授权密码
EMAIL_HOST_PASSWORD = 'xxxx'
# 收件人看到的发件人
# EMAIL_FROM = '[email protected]'
EMAIL_FROM = 'xxx'
然后创建一个celery的模块包,里面的task.py文件写下发邮件的脚本:
#from django.core.mail import send_mail
from celery.main import app
from django.conf import settings
import random
import smtplib
from email.mime.text import MIMEText
from email.header import Header
@app.task(name='send_verify_mail', bing=True)
def send_verify_mail(to_email, code):
sender = settings.EMAIL_HOST_USER
receiver = [to_email]
subject = '放假通知'
username = settings.EMAIL_HOST_USER
password = settings.EMAIL_HOST_PASSWORD
msg = MIMEText('hello code %s' % code,
_charset='utf-8') #中文需参数‘utf-8',单字节字符不需要
msg['Subject'] = Header(subject, 'utf-8')
msg['From'] = settings.EMAIL_FROM
msg['To'] = to_email
smtp = smtplib.SMTP_SSL()
smtp.connect(settings.EMAIL_HOST)
smtp.login(username, password)
smtp.sendmail(sender, receiver, msg.as_string())
smtp.quit()
然后后面如果要发送邮件,只要调用这个函数,并启动celery就行了,但这里就出现了非常多的坑是各种邮箱完全不相同的验证,另外还有相关的HOST和PORT,这里将关于POP3/STMP/IMAP三种对应端口的情况列成一张表格为:
邮箱类型 | 服务器名称 | 服务器地址 | SSL协议端端口 | 端口 |
QQ邮箱 | POP3 | pop.qq.com | 110 | |
SMTP | smtp.qq.com | 465 | 25 | |
IMAP | imap.qq.com | 993 | ||
163邮箱 | POP3 | pop.163.com | 110 | |
SMTP | smtp.163.com | 465 | 25 | |
IMAP | imap.163.com | 993 | ||
gmail邮箱 | POP3 | pop.gmail.com | 995 | |
SMTP | smtp.gmail.com | 587 / 465 | 25(我试验是可以) | |
IMAP | imap.gmail.com | 993 | ||
yahoo邮箱 | POP3 | pop.mail.yahoo.com | 110 | |
SMTP | smtp.mail.yahoo.com | 25 | ||
IMAP | imap.mail.yahoo.com | 993 | ||
表中列举到的,除了谷歌邮箱,其它都是用的国内的邮箱账号,我们主要是看STMP协议这一项,如果不做设置,可能会出现如下的情况:
这里就需要我们去开启自己邮箱里的相关设置,这里我详细说的是QQ和谷歌,因为这两个也是我试验得最久的两个了。
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.qq.com'
MAIL_USE_DEBUG = True
MAIL_USE_TLS = True
# EMAIL_HOST = 'smtp.maxhichina.com'
EMAIL_PORT = 465
# 发送邮件的邮箱
EMAIL_HOST_USER = '[email protected]'
# 在邮箱中设置的客户端授权密码
EMAIL_HOST_PASSWORD = 'xxx'
# 收件人看到的发件人
EMAIL_FROM = 'Tim<[email protected]>'
一般如果照上面这么写基本上是没啥问题的,可能Email_port改成25也能发送邮件,那表示是未加密的网关,可能就会自动判别为垃圾邮件了,如果是465的话更保险些。QQ邮箱还是没有啥太大的坑,最多全部都是退信,但Gmail的套路就可能是一环接一环了。
上述国内邮箱的设置还能用Foxmail软件直接设置,QQ邮箱中就有一个很详细的说明:Foxmail常用邮件客户端软件设置
gmail邮箱是没有授权码的,应该说国外的邮箱好像很少要授权码,都是直接拿密码当成授权码用,但这个里面的坑就多了。
其实我写这篇文章的原因也是我在gmail邮箱上试了很长一段时间,原因是这个坑有点深,也可能我的搜索方式不对。当我直接用密码房授权码时,发送邮件的时候会报错为:
b"5.7.8 username or password not accept,那么也能说明这个密码是无效的。在此之前,还遇到的bug是newwork is unreachable,即是网络达不到要求,那个是因为之前用的阿里云国内服务器,然后用的25网关,但找了半天,发现好像不适用,阿里云的STMP服务地址为:
所以在这里提一下,因为当时我是拿我自己服务器测试,发现谷歌连接不上了。但项目是在国外服务器上,所以我直接在国外服务器上把邮箱替换成谷歌邮箱,然后回到这个错误:b"5.7.8 username or password not accept
然后翻遍了Stack Overflow还有一些国外怎么设置谷歌邮箱的帖子,发现了三步:
我们可以进入https://myaccount.google.com/lesssecureapps界面将设置开起来,最好是用gmail浏览器已经进行过登录,那么就能跳过一些繁琐的登录。
开启后,重新以STMP协议发送邮件,然后这个时候可能就会报错为:b"5.7.7,图没有截了,大致意思应该说是验证密码错误,也就是说开了这个后不能登录上去,如果没用,那么就下一步。
进入https://accounts.google.com/b/0/DisplayUnlockCaptcha 页面中,Google 可能會在您透過新的裝置或應用程式登入時,要求您完成這項額外步驟。如要授權存取,請按下方的 [繼續] 按鈕。然后我们就进入到了授权码的选择界面,
一路选是就OK了,之后可以再次尝试,如果还是不成功,这里报啥错忘了,如果还是不成功,那么就最后一步。
第三步也是最后一步,如果还是不行,那只能采用这种冒险的方式,为啥说冒险呢,如果真的要做这步,就很有可能被封号几天,我当时是啥也不懂,看到有人这样搞,然后我也去搞了一波,但结果就不太好了。
这一步需要我们进入网址:https://myaccount.google.com/signinoptions/two-step-verification/enroll-welcome?utm_source=google-account&utm_medium=web,然后手动去获取一个授权码,并将密码设置为授权码的方式。
中间过程我就不再说了,当时配置的时候没有截图,这里默认有五种可以选,有windows、mac、Android、iPhone还有其它,这里选择其它直接确定,就能拿到授权码,然后把密码改完后感觉挺好的,但第二天就被封号了,可能我一次申请的有点多,也可能是其它各种原因,所以奉劝看到这里的朋友,别轻易尝试这种,如果上述两步没用,那么就可以考虑换邮箱了,下面是我的惨痛教训:
然后是申请复查,天地良心,我还没开始搞啥破坏呢。。。
中间还有 b"5.7.14、b"5.7.4、b"5.7.7,我记得其中分别是认证密码错误,stmp连接错误等,如果走完上面两步还出现这类问题,第一是看拼写有没有问题,如果和我上面写的QQ格式基本一致,端口是587或者465没用,那可以尝试重新走一遍上述步骤,可能中间哪里没有开起来,或者按第三步来,但我实验是被封了两天。
后来还尝试了雅虎还有网易企业邮箱,我都不知道我为什么要试这么多种邮箱,因为最近一个项目是海外项目,老板搞了各种邮箱,但一直想换,那没办法咯,我就只能陪着他换。企业邮箱的话就是要做域名解析,如果是阿里云服务器的话,这里就不再说了,直接后台改就完事了。感觉这篇文章算一个科普性质的,科普我从错误里的经验,还有就是邮箱咋设置,我感觉现在啥邮箱都能搞一搞了。。。