Flask插件系列 - Flask-Mail
簡介
給用戶發(fā)送郵件是 Web 應(yīng)用中最常見的任務(wù)之一,比如用戶注冊,找回密碼等。Python 內(nèi)置了一個(gè) smtplib 的模塊,可以用來發(fā)送郵件,這里我們使用 Flask-Mail,是因?yàn)樗梢院?Flask 集成,讓我們更方便地實(shí)現(xiàn)此功能。
安裝
使用 pip 安裝:
- $ pip install Flask-Mail
或下載源碼安裝:
- $ git clone https://github.com/mattupstate/flask-mail.git
- $ cd flask-mail
- $ python setup.py install
發(fā)送郵件
Flask-Mail 連接到簡單郵件傳輸協(xié)議 (Simple Mail Transfer Protocol, SMTP) 服務(wù)器,并把郵件交給這個(gè)服務(wù)器發(fā)送。這里以 QQ 郵箱為例,介紹如何簡單地發(fā)送郵件。在此之前,我們需要知道 QQ 郵箱的服務(wù)器地址和端口是什么,點(diǎn)此查看。
- # -*- coding: utf-8 -*-
- from flask import Flask
- from flask_mail import Mail, Message
- import os
- app = Flask(__name__)
- app.config['MAIL_SERVER'] = 'smtp.qq.com' # 郵件服務(wù)器地址
- app.config['MAIL_PORT'] = 25 # 郵件服務(wù)器端口
- app.config['MAIL_USE_TLS'] = True # 啟用 TLS
- app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME') or 'me@example.com'
- app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD') or '123456'
- mail = Mail(app)
- @app.route('/')
- def index():
- msg = Message('Hi', sender='me@example.com', recipients=['he@example.com'])
- msg.html = '<b>Hello Web</b>'
- # msg.body = 'The first email!'
- mail.send(msg)
- return '<h1>OK!</h1>'
- if __name__ == '__main__':
- app.run(host='127.0.0.1', debug=True)
在發(fā)送前,需要先設(shè)置用戶名和密碼,當(dāng)然你也可以直接寫在文件里,如果是從環(huán)境變量讀取,可以這么做:
- $ export MAIL_USERNAME='me@example.com'
- $ export MAIL_PASSWORD='123456'
將上面的 sender 和 recipients 改一下,就可以進(jìn)行測試了。
從上面的代碼,我們可以知道,使用 Flask-Mail 發(fā)送郵件主要有以下幾個(gè)步驟:
- 配置 app 對象的郵件服務(wù)器地址,端口,用戶名和密碼等
- 創(chuàng)建一個(gè) Mail 的實(shí)例:mail = Mail(app)
- 創(chuàng)建一個(gè) Message 消息實(shí)例,有三個(gè)參數(shù):郵件標(biāo)題、發(fā)送者和接收者
- 創(chuàng)建郵件內(nèi)容,如果是 HTML 格式,則使用 msg.html,如果是純文本格式,則使用 msg.body
- 最后調(diào)用 mail.send(msg) 發(fā)送消息
Flask-Mail 配置項(xiàng)
Flask-Mail 使用標(biāo)準(zhǔn)的 Flask 配置 API 進(jìn)行配置,下面是一些常用的配置項(xiàng):
配置項(xiàng) | 說明 |
---|---|
MAIL_SERVER | 郵件服務(wù)器地址,默認(rèn)為 localhost |
MAIL_PORT | 郵件服務(wù)器端口,默認(rèn)為 25 |
MAIL_USE_TLS | 是否啟用傳輸層安全 (Transport Layer Security, TLS)協(xié)議,默認(rèn)為 False |
MAIL_USE_SSL | 是否啟用安全套接層 (Secure Sockets Layer, SSL)協(xié)議,默認(rèn)為 False |
MAIL_DEBUG | 是否開啟 DEBUG,默認(rèn)為 app.debug |
MAIL_USERNAME | 郵件服務(wù)器用戶名,默認(rèn)為 None |
MAIL_PASSWORD | 郵件服務(wù)器密碼,默認(rèn)為 None |
MAIL_DEFAULT_SENDER | 郵件發(fā)件人,默認(rèn)為 None,也可在 Message 對象里指定 |
MAIL_MAX_EMAILS | 郵件批量發(fā)送個(gè)數(shù)上限,默認(rèn)為 None |
MAIL_SUPPRESS_SEND | 默認(rèn)為 app.testing,如果為 True,則不會(huì)真的發(fā)送郵件,供測試用 |
異步發(fā)送郵件
使用上面的方式發(fā)送郵件,會(huì)發(fā)現(xiàn)頁面卡頓了幾秒才出現(xiàn)消息,這是因?yàn)槲覀兪褂昧送降姆绞?。為了避免發(fā)送郵件過程中出現(xiàn)的延遲,我們把發(fā)送郵件的任務(wù)移到后臺(tái)線程中,代碼如下:
- # -*- coding: utf-8 -*-
- from flask import Flask
- from flask_mail import Mail, Message
- from threading import Thread
- import os
- app = Flask(__name__)
- app.config['MAIL_SERVER'] = 'smtp.qq.com'
- app.config['MAIL_PORT'] = 25
- app.config['MAIL_USE_TLS'] = True
- app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME') or 'smtp.example.com'
- app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD') or '123456'
- mail = Mail(app)
- def send_async_email(app, msg):
- with app.app_context():
- mail.send(msg)
- @app.route('/sync')
- def send_email():
- msg = Message('Hi', sender='me@example.com', recipients=['he@example.com'])
- msg.html = '<b>send email asynchronously</b>'
- thr = Thread(target=send_async_email, args=[app, msg])
- thr.start()
- return 'send successfully'
- if __name__ == '__main__':
- app.run(host='127.0.0.1', debug=True)
在上面,我們創(chuàng)建了一個(gè)線程,執(zhí)行的任務(wù)是send_async_email,該任務(wù)的實(shí)現(xiàn)涉及一個(gè)問題1:
很多 Flask 擴(kuò)展都假設(shè)已經(jīng)存在激活的程序上下文和請求上下文。Flask-Mail 中的 send() 函數(shù)使用 current_app,因此必須激活程序上下文。不過,在不同線程中執(zhí)行 mail.send() 函數(shù)時(shí),程序上下文要使用 app.app_context()人工創(chuàng)建。
帶附件的郵件
有時(shí)候,我們發(fā)郵件的時(shí)候需要添加附件,比如文檔和圖片等,這也很簡單,代碼如下:
- # -*- coding: utf-8 -*-
- from flask import Flask
- from flask_mail import Mail, Message
- import os
- app = Flask(__name__)
- app.config['MAIL_SERVER'] = 'smtp.qq.com' # 郵件服務(wù)器地址
- app.config['MAIL_PORT'] = 25 # 郵件服務(wù)器端口
- app.config['MAIL_USE_TLS'] = True # 啟用 TLS
- app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME') or 'me@example.com'
- app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD') or '123456'
- mail = Mail(app)
- @app.route('/attach')
- def add_attchments():
- msg = Message('Hi', sender='me@example.com', recipients=['other@example.com'])
- msg.html = '<b>Hello Web</b>'
- with app.open_resource("/Users/Admin/Documents/pixel-example.jpg") as fp:
- msg.attach("photo.jpg", "image/jpeg", fp.read())
- mail.send(msg)
- return '<h1>OK!</h1>'
- if __name__ == '__main__':
- app.run(host='127.0.0.1', debug=True)
上面的代碼中,我們通過 app.open_resource(path_of_attachment) 打開了本機(jī)的某張圖片,然后通過msg.attach() 方法將附件內(nèi)容添加到 Message 對象。msg.attach() 方法的第一個(gè)參數(shù)是附件的文件名,第二個(gè)參數(shù)是文件內(nèi)容的 MIME (Multipurpose Internet Mail Extensions) 類型,第三個(gè)參數(shù)是文件內(nèi)容。
如果你不知道附件的 MIME 類型是什么,可以查看 MIME 參考手冊。
批量發(fā)送
在某些情況下,我們需要批量發(fā)送郵件,比如給網(wǎng)站的所有注冊用戶發(fā)送改密碼的郵件,這時(shí)為了避免每次發(fā)郵件時(shí)都要?jiǎng)?chuàng)建和關(guān)閉跟服務(wù)器的連接,我們的代碼需要做一些調(diào)整,類似如下:
- with mail.connect() as conn:
- for user in users:
- subject = "hello, %s" % user.name
- msg = Message(recipients=[user.email], body='...', subject=subject)
- conn.send(msg)
上面的工作方式,使得應(yīng)用與電子郵件服務(wù)器保持連接,一直到所有郵件已經(jīng)發(fā)送完畢。某些郵件服務(wù)器會(huì)限制一次連接中的發(fā)送郵件的上限,這樣的話,你可以配置 MAIL_MAX_EMAILS。
需要注意的是,更好的發(fā)送大量電子郵件的方式是用專門的作業(yè)系統(tǒng),比如用 Celery 任務(wù)隊(duì)列等。
本文完整的代碼在這里。