flask 电子邮件Flask-Mail

时间:2024-01-01 23:24:15

电子邮件

在web程序中,经常会需要发送电子邮件。比如,在用户注册账户时发送确认邮件;定期向用户发送热门内容或是促销信息等等。在Web程序中发送电子邮件并不复杂,借助扩展Flask-Mail或是第三方邮件服务,只需要几行代码就可以发送邮件。

下面例子中,我们使用一封示例邮件,邮件仅包含几个必要的字段,如下:

flask 电子邮件Flask-Mail

标准的收信方和发信方字符串由姓名和邮箱地址两部分组成,,二者由空格相隔,比如“姓名 <Email地址>”。字符串中的姓名是可选的,收信方一般可以不写姓名,这时可以直接写出邮箱地址,比如”hello@example.com”。

使用Flask-Mail发送电子邮件

扩展Flask-Mail包装了python标准库中的smtplib包,简化了Flask程序中发送电子邮件的过程。我们使用pipenv安装Flask-Mail:

(Lenovo-ezd1lI9Y) C:\Users\Lenovo>pipenv install flask-mail

Installing flask-mail...

Adding flask-mail to Pipfile's [packages]...

Installation Succeeded

flask 电子邮件Flask-Mail

和其他扩展类似,我们实例化Flask-Mail提供的Mail类并传入程序实例以完成初始化,如下所示:

from flask_mail import Mail

app = Flask(__name__)
mail = Mail(app)

配置Flask-Mail
Flask_Mail通过链接SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)服务器来发送邮件。因此,在开始发送电子邮件前,我们需要配置SMTP服务器。如果电脑上已经设置了SMTP服务器,那么无需过多的配置即可使用,默认的邮件服务器配置即为localhost,端口为25.
在开发和测试阶段,我们可以使用邮件服务提供商的SMTP服务器(比如Bmail),这时我们需要对Flask-Mail进行配置。下面列出了Flask-Mail提供的常用配置变量。

flask 电子邮件Flask-Mail



对发送的邮件进行加密可以避免邮件在发送过程中被第三方截获和篡改。SSL(Security Socket Layer,安全套接字层)和TLS(Transport Layer Sceurity,传输层安全)是两种常用的电子邮件安全协议。TLS继承了SSL,并在SSL的基础上做了一些改进(TLS是后期版本的SSL)。所以,在大多数情况下,名词SSL和TLS可以互换使用。他们通过将MAIL_USE_SSL设置为True开启。STARTTLS是另一种加密方式,它会对不安全的连接进行升级(使用SSL或TLS)。尽管它的名字中包含TLS,但也可能会使用SSL加密。根据加密的方式不同,端口也要相应改变,如下所示:
SSL/TLS加密

MAIL_USE_SSL = True

MAIL_PORT = 465

STARTTLS加密

MAIL_USE_TLS = True

MAIL_PORT = 587

当不对邮件进行加密时,邮件服务器的端口使用默认的25端口。

常用的电子邮箱服务提供商的SMTP配置信息如下所示:

flask 电子邮件Flask-Mail

163邮箱的SMTP服务器不支持STARTTLS,你需要使用SSL/TLS加密。集体来说就是将MAIL_USE_SSL设为True,MAIL_PORT设为465.

要使用这些邮箱服务,需要访问对应的网站注册一个账户。开启邮箱的SMTP服务和获取授权码等操作均可以在个邮箱主页-设置(-账户)中找到。

Gmail、Outlook、QQ邮箱等这类服务被称为EPA(Email Service Provider),只适用于个人业务使用,不适合用来发送事务邮件(Transactional Email)。对于需要发送大量邮件的事务型邮件任务,更好的选择是使用自己设置的SMTP服务器或是类似SendGrid、Mailgun的事务邮件服务提供商(Transactional Email Service),后边会具体介绍。

在程序中,随着配置逐渐增多,我们改用app.config对象的update()方法来加载配置:

app.py:邮件服务器配置

from flask import Flask
from flask_mail import Mail app = Flask(__name__)
app.config.update(
MAIL_SERVER = os.getenv('MAIL_SERVER'),
MAIL_PORT = 678,
MAIL_USE_TLS = True,
MAIL_USEERNAME = os.getenv('MAIL_USERNAME'),
MAIL_PASSWORD = os.getenv('MAIL_PASSWORD'),
MAIL_DEFAULT_SENDER = ('Grey Li', os.getenv('MAIL_USERNAME'))
) mail = Mail(app)

在实例化Mail类时,Flask-Mail会获取配置以创建一个用于发信的对象,所以确保在实例化Mail类之前加载配置。

在我们的配置中,邮箱账户的密码属于敏感信息,不能直接写在脚本中,所以设置为从系统变量中获取。另外,在生产环境中,我们通常会使用不同的邮件服务器地址,所以这里也从环境变量中读取。

你可以使用export/set命令设置环境变量,为了方便管理,我们把这些环境变量存储在.env文件中:

MAIL_SERVER = 'smtp.qq.com'
MAIL_USERNAME = '367224698@qq.com'
MAIL_PASSWORD = 'ljfitqzfphlibjdj'

注意MAIL_PASSWORD是在qq邮箱的设置-账户中通过手机发送短信,获取的授权码,并不是qq的密码,需要开发smtp服务

flask 电子邮件Flask-Mail


默认发信人由一个两元素元祖组成,即(姓名,邮箱地址),比如:

MAIL_DEFAULT_SENDER = ('Your Name', 'your_name@example.com')

需要注意,使用邮件服务提供商提供的SMTP服务器发信时,发信人字符串中的邮件地址必须和邮箱地址相同,你可以直接只用MAIL_USERNAME的值构建发信人地址:

MAIL_DEFAULT_SENDER = ('Your Name', os.getenv('MAIL_USERNAME'))

Flask-Mail会把这个元祖转换为标准的发信人格式,即Your Name <your_name@example.com>。 你可以直接以这种方式指定发信人,比如:

MAIL_DEFAULT_SENDER = 'Your Name <your_name@example.com>'

设置默认发信人后,在发信时就可以不用再指定发信人。

构建邮件数据

下面我们借助Python shell演示发送邮件的过程。邮件通过从Flask-Mail中导入的Message类表示,而发信功能通过我们在程序包的构造文件中创建的mail对象实现,我们先进性导入:

>>> from flask_mail import Message
>>> from app import mail

一封邮件至少要包含主题、收件人、正文、发信人这几个元素。发信人(sender)在前面我们已经使用MAIL_DEFAULT_SENDER配置变量指定过了,剩下的分别通过Message类的构造方法中的subject、recipients、body关键字传入参数,其中recipients为一个包含电子邮件地址的列表。

message = Message(subject = 'title', recipients=['367224698@qq.com'], body='body')
加载环境变量方式:

1、flask项目可以通过.env加载环境变量,加载方式是运行 flask run或flask shell时加载

2、pipenv也可以通过.env加载环境变量,进入pipenv shell虚拟环境后,修改.env环境变量后再启动flask app: flask run,flask还是会用原来的环境变量,原因是pipenv shell加载了环境变量并进行了缓存,然后flask加载环境变量时不会进行覆盖。

此时可以退出pipenv shell然后再重新进入

和发信人字符串类似,收信人字符串可以为两种形式:

‘Sam <sam@example.com>’或’sam@example.com’。

发送邮件
>>>mail.send(message)

完整的发送实例邮件的代码如下:

>>> from flask_mail import Message
>>> from app import mail
>>> app.config['MAIL_PORT']
''
>>> message = Message(subject = 'title', recipients=['367224698@qq.com'], body='body')
>>> mail.send(message)

在实际操作时,报了错误:SMTPSenderRefused: (503, 'Error: need EHLO and AUTH first !', u'367224698@qq.com'),原因尚未搞清楚,配置都感觉ok,但还是报错,怀疑是QQ邮箱服务器以为我的恶意的客户端。。。

>>> from flask_mail import Message
>>> from app import mail
>>> app.config['MAIL_PORT']
''
>>> message = Message(subject = 'title', recipients=['367224698@qq.com'], body='body')
>>> mail.send(message)
send: 'ehlo [172.20.10.4]\r\n'
reply: '250-smtp.qq.com\r\n'
reply: '250-PIPELINING\r\n'
reply: '250-SIZE 73400320\r\n'
reply: '250-AUTH LOGIN PLAIN\r\n'
reply: '250-AUTH=LOGIN\r\n'
reply: '250-MAILCOMPRESS\r\n'
reply: '250 8BITMIME\r\n'
reply: retcode (250); Msg: smtp.qq.com
PIPELINING
SIZE 73400320
AUTH LOGIN PLAIN
AUTH=LOGIN
MAILCOMPRESS
8BITMIME
send: u'mail FROM:<367224698@qq.com> size=263\r\n'
reply: '503 Error: need EHLO and AUTH first !\r\n'
reply: retcode (503); Msg: Error: need EHLO and AUTH first !
send: 'rset\r\n'
reply: '250 Ok\r\n'
reply: retcode (250); Msg: Ok
send: 'quit\r\n'
reply: '221 Bye\r\n'
reply: retcode (221); Msg: Bye
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "c:\users\lenovo\.virtualenvs\lenovo-ezd1li9y\lib\site-packages\flask_mail.py", line 492, in send
message.send(connection)
File "c:\users\lenovo\.virtualenvs\lenovo-ezd1li9y\lib\site-packages\flask_mail.py", line 427, in send
connection.send(self)
File "c:\users\lenovo\.virtualenvs\lenovo-ezd1li9y\lib\site-packages\flask_mail.py", line 192, in send
message.rcpt_options)
File "c:\python27\Lib\smtplib.py", line 737, in sendmail
raise SMTPSenderRefused(code, resp, from_addr)
SMTPSenderRefused: (503, 'Error: need EHLO and AUTH first !', u'367224698@qq.com')
flask_mail发送163邮件

下面用163邮箱试一下

先进入163邮件开启smtp服务,获取授权码

授权码就是客户端登录163邮箱时的密码,用户名就是邮箱地址

flask 电子邮件Flask-Mail

flask 电子邮件Flask-Mail

flask 电子邮件Flask-Mail

获取授权码后,记录下来

在.env文件中设置邮箱信息,在命令行中运行flask shell时,会自动到该文件中获取环境变量

email\.env:

FLASK_ENV=development
MAIL_SERVER = 'smtp.163.com'
MAIL_USERNAME = 'xiaxiaoxu1987@163.com'
MAIL_PASSWORD = 'FOREVER022941' email\app.py:
from flask import Flask from flask_mail import Mail, Message import os app = Flask(__name__)
app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True #设置app.config信息,重点是用户名和密码
app.config.update(
SECRET_KEY = "SECRET KEY",
MAIL_SERVER = os.getenv('MAIL_SERVER'),
MAIL_PORT = 465,
#MAIL_PORT = 587,
#MAIL_USE_TLS = True,
MAIL_USE_SSL = True,
MAIL_USERNAME = os.getenv('MAIL_USERNAME'),
MAIL_PASSWORD = os.getenv('MAIL_PASSWORD'),
MAIL_DEFAULT_SENDER = (os.getenv('MAIL_USERNAME'))
)

命令行界面,操作发送邮件

>>> from flask_mail import Message
>>> from app import mail
>>> msg = Message('text', sender = 'xiaxiaoxu1987@163.com', recipients=['367224698@qq.com'])
>>> msg.body = 'body'
>>> mail.send(msg)
send: 'ehlo [172.20.10.4]\r\n'
reply: '250-mail\r\n'
reply: '250-PIPELINING\r\n'
reply: '250-AUTH LOGIN PLAIN\r\n'
reply: '250-AUTH=LOGIN PLAIN\r\n'
reply: '250-coremail 1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UrHJsU2UCa0xDrUUUUj\r\n'
reply: '250-STARTTLS\r\n'
reply: '250 8BITMIME\r\n'
reply: retcode (250); Msg: mail
PIPELINING
AUTH LOGIN PLAIN
AUTH=LOGIN PLAIN
coremail 1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UrHJsU2UCa0xDrUUUUj
STARTTLS
8BITMIME
send: 'AUTH PLAIN AHhpYXhpYW94dTE5ODdAMTYzLmNvbQBGT1JFVkVSMDIyOTQx\r\n'
reply: '235 Authentication successful\r\n'
reply: retcode (235); Msg: Authentication successful
send: u'mail FROM:<xiaxiaoxu1987@163.com>\r\n'
reply: '250 Mail OK\r\n'
reply: retcode (250); Msg: Mail OK
send: u'rcpt TO:<367224698@qq.com>\r\n'
reply: '250 Mail OK\r\n'
reply: retcode (250); Msg: Mail OK
send: 'data\r\n'
reply: '354 End data with <CR><LF>.<CR><LF>\r\n'
reply: retcode (354); Msg: End data with <CR><LF>.<CR><LF>
data: (354, 'End data with <CR><LF>.<CR><LF>')
send: 'Content-Type: text/plain; charset="utf-8"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 7bit\r\nSubject: text\r\nFrom: xiaxiaoxu1987@163.com\r\nTo: 367224698@qq.com\r\nDate: Wed, 10 Apr 2019 21:13:57 +0800\r\nMessage-ID: <155490203514.14300.10708927723623278864@DESKTOP-F82U4NJ>\r\n\r\nbody\r\n.\r\n'
reply: '250 Mail OK queued as smtp11,D8CowACXrmER7K1cTRnVFA--.36353S2 1554902035\r\n'
reply: retcode (250); Msg: Mail OK queued as smtp11,D8CowACXrmER7K1cTRnVFA--.36353S2 1554902035
data: (250, 'Mail OK queued as smtp11,D8CowACXrmER7K1cTRnVFA--.36353S2 1554902035')
send: 'quit\r\n'
reply: '221 Bye\r\n'
reply: retcode (221); Msg: Bye

查看邮箱发送结果:

flask 电子邮件Flask-Mail

为了方便重用,我们把这些代码包装成一个通用的发信函数send_mail(),如下所示:

app.py: 通用发信函数

from flask import Flask
from flask_mail import Mail, Message
import os app = Flask(__name__)
app.config.update(
MAIL_SERVER = os.getenv('MAIL_SERVER'),
MAIL_PORT = '',
#MAIL_USE_TLS = True,
MAIL_USE_SSL = True,
MAIL_USEERNAME = os.getenv('MAIL_USERNAME'),
MAIL_PASSWORD = os.getenv('MAIL_PASSWORD'),
MAIL_DEFAULT_SENDER = (os.getenv('MAIL_USERNAME'))
) mail = Mail(app) def send_mail(subject, to, body):
message = Message(subject, recipients = [to], body = body)
mail.send(message)

假设我们的程序时一个周刊订阅程序,当用户在表单中填写了正确的email地址时,我们就发送一封邮件来通知用户订阅成功。通过在index视图中调用send_email()即可发送邮件,如下所示: 在视图函数中发送邮件

@app.route('/subscribe', methods = ['GET', 'POST'])
def subscribe():
form = SubscribeForm()
if form.validate_on_submit():
name = form.name.data
email = form.email.data
send_subscribe_mail('Subscribe Success!', email, name = name)
flash('Confirmation email have been sent! Check your inbox.')
return redirect(url_for('subscribe'))
return render_template('subscribe.html', form = form)