在 Python 標準庫中通常使用 smtplib 包發送電子郵件,而 Flask 中的 Flask-Mail 擴展不僅包裝了 smtplib,且能更好的與 Flask 集成。首先在虛擬環境中安裝此擴展:
pip install flask-mail
Flask-Mail文檔:http://www.pythondoc.com/flask-mail/index.html
一、配置
Flask-Mail 連接到簡單郵件傳輸協議(SMTP,simple mail transferprotocol)服務器,把郵件交給這個服務器發送。如果不進行配置,則 Flask-Mail 連接 localhost 上的 25 端口,無須驗證身份即可發送電子郵件。
Flask-Mail SMTP 服務器配置:
配置 | 默認值 | 說明 |
---|---|---|
MAIL_SERVER | localhost | 電子郵件服務器的主機名或 IP 地址 |
MAIL_PORT | 25 | 電子郵件服務器的端口 |
MAIL_USE_TLS | False | 啓用傳輸層安全(TLS,transport layer security)協議 |
MAIL_USE_SSL | False | 啓用安全套接層(SSL,secure sockets layer)協議 |
MAIL_USERNAME | None | 郵件賬戶的用戶名 |
MAIL_PASSWORD | None | 郵件賬戶的密碼 |
實際中,連接到外部 SMTP 服務器更方便,如下例使用 qq郵箱的配置:
import os
# ...
app.config['MAIL_SERVER'] = 'smtp.qq.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
注意qq郵箱需要先開啓SMTP服務,並得到授權碼:
MAIL_USERNAME爲郵箱號,MAIL_PASSWORD 的值即爲生成的授權碼。
由於QQ郵箱不支持非加密的協議,那麼使用加密協議,分爲兩種加密協議,選擇其中之一即可
- MAIL_USE_TLS:端口號是587
- MAIL_USE_SSL:端口號是465
Flask-Mail在使用前也需要進行初始化:
from flask_mail import Mail
mail = Mail(app)
保存電子郵件服務器用戶名和密碼的兩個環境變量要在環境中定義。如果你使用的是 Linux 或 macOS,可以按照下面的方式設定這兩個變量:
export MAIL_USERNAME=<mail username>
export MAIL_PASSWORD=<mail password>
微軟 Windows 用戶可按照下面的方式設定環境變量:
# cmd終端
set MAIL_USERNAME=<mail username>
set MAIL_PASSWORD=<mail password>
# powershell終端
$env:MAIL_USERNAME='<mail username>'
$env:MAIL_PASSWORD='<mail password>'
二、在Python shell中發送電子郵件
打開一個 shell 會話(powershell),來發送一個測試郵件。
先配置一下環境變量,上面的方式定義的是臨時環境變量,每個新shell都需要導入一次。
$env:FLASK_APP='.\hello.py'
$env:MAIL_USERNAME='[email protected]'
$env:MAIL_PASSWORD='你的授權碼'
然後打開 flask shell 進行測試:
>>> from flask_mail import Message
>>> from hello import mail
>>> msg = Message('test email', sender='[email protected]', recipients=['[email protected]'])
>>> msg.body = 'This is the plain text body'
>>> msg.html = 'This is the <b>HTML</b> body'
>>> with app.app_context():
... mail.send(msg)
注意:Flask-Mail 的 send() 函數使用 current_app ,因此要在激活的應用上下文中執行。
成功收到郵件:
三、在應用中集成郵件發送功能
一般把發送電子郵件的部分定義爲一個函數,這樣還可以使用 Jinja2 模板渲染郵件正文,靈活性高。
from flask_mail import Message
# 主題的前綴
app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]'
# 發件人地址
app.config['FLASKY_MAIL_SENDER'] = 'Flasky Admin <[email protected]>'
def sned_email(to, subject, template, **kwargs):
msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
msg.body = render_template(template + '.txt', **kwargs)
msg.html = render_template(template + '.html', **kwargs)
mail.send(msg)
- send_mail() 函數的參數分別爲收件人地址(to)、主題(subject)、渲染郵件正文的模板(template)、關鍵字參數列表(**kwargs)。
- 指定模板時不包含擴展名,這樣才能使用兩個模板分別渲染txt和HTML。
- 調用者傳入
的關鍵字參數將傳給 render_template() 函數,作爲模板變量提供給模板使用,用於生成電子郵件正文。
下面我們修改視圖函數 index(),使表單每接收到新的名字,應用就給管理員發送一封電子郵件,修改hello.py如下:
# ...
app.config['FLASKY_ADMIN'] = os.environ.get('FLASKY_ADMIN')
# ...
@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.name.data).first()
if user is None:
user = User(username=form.name.data)
db.session.add(user)
db.session.commit()
session['known'] = False
# 發送電子郵件
if app.config['FLASKY_ADMIN']:
sned_email(app.config['FLASKY_ADMIN'], 'New User',
'mail/new_user', user=user)
else:
session['known'] = True
session['name'] = form.name.data
session['message'] = user.message
form.name.data = ''
return redirect(url_for('index'))
return render_template('index.html',
form=form, name=session.get('name'),
known=session.get('known', False),
message=session.get('message'))
templates/mai/new_user.txt:
User {{ user.username }} has joined.
templates/mai/new_user.html:
User <b>{{ user.username }}</b> has joined.
- 電子郵件的收件人地址保存在環境變量 FLASKY_ADMIN 中,啓動前需要導入此環境變量,方法和前面的相同。
- 我們還需要創建兩個模板文件,分別用於渲染純文本和HTML版本的郵件正文。這兩個模板文件都保存在 templates 目錄下的 mail 子目錄中。
- 電子郵件的模板中有一個模板參數是用戶,因此調用 send_email() 函數時要以關鍵字參數的形式傳入用戶。
現在每次你在表單中填寫新名字(如email test),管理員(FLASKY_ADMIN)都會收到一封電子郵件。
四、異步發送電子郵件
在上面的例子中,我們發現在發送電子郵件的時候,網頁會停滯一會,爲了避免用戶感覺到這樣的延遲,可以把發送電子郵件的函數移到後臺線程中,修改方法如下:
from threading import Thread
# ...
def send_async_email(app, msg):
with app.app_context():
mail.send(msg)
def sned_email(to, subject, template, **kwargs):
msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
msg.body = render_template(template + '.txt', **kwargs)
msg.html = render_template(template + '.html', **kwargs)
thr = Thread(target=send_async_email, args=[app, msg])
thr.start()
return thr
很多 Flask 擴展都假設已經存在激活的應用上下文和(或)請求上下文。Flask-Mail 的 send() 函數使用 current_app ,因此必須激活應用上下文。
不過,上下文是與線程配套的,在不同的線程中執行 mail.send() 函數時,要使用 app.app_context() 人工創建應用上下文。app 實例作爲參數傳入線程,因此可以通過它來創建上下文。