第六章 電子郵件

很多類型的應(yīng)用程序都需要在特定事件發(fā)生時(shí)(例如注冊(cè))提醒用戶, 而常用的通信方法是電子郵件。

使用 Flask-Mail 提供電子郵件支持

pip install flask-mail

Flask-Mail SMTP服務(wù)器的配置

配  置 默認(rèn)值 說(shuō)  明
MAIL_SERVER localhost 電子郵件服務(wù)器的主機(jī)名或 IP 地址
MAIL_PORT 25 電子郵件服務(wù)器的端口
MAIL_USE_TLS False 啟用傳輸層安全(Transport Layer Security,TLS)協(xié)議
MAIL_USE_SSL False 啟用安全套接層(Secure Sockets Layer,SSL)協(xié)議
MAIL_USERNAME None 郵件賬戶的用戶名
MAIL_PASSWORD None 郵件賬戶的密碼

在開發(fā)過(guò)程中, 如果連接到外部 SMTP 服務(wù)器, 則可能更方便。下面使用新浪郵箱賬戶發(fā)送電子郵件:

示例 6-1 hello.py:配置 Flask-Mail 使用新浪郵箱

import os
# ...
app.config['MAIL_SERVER'] = 'smtp.sina.com'
app.config['MAIL_PORT'] = 25
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')

千萬(wàn)不要把賬戶密碼直接寫入腳本, 特別是你計(jì)劃開源自己的作品時(shí)。為了保護(hù)賬戶信息, 你需要讓腳本從環(huán)境中導(dǎo)入敏感信息。

Flask-Mail 的初始化方法如下:

示例 6-2 hello.py:初始化 Flask-Mail

from flask_mail import Mail
mail = Mail(app)

保存電子郵件服務(wù)器用戶名和密碼的兩個(gè)環(huán)境變量要在環(huán)境中定義。如果你在 Linux 或 Mac OS X 中使用 bash, 則這樣設(shè)置這兩個(gè)變量:

(venv) $ export MAIL_USERNAME="ohmydog"
(venv) $ export MAIL_PASSWORD="ohmydog@sina.com"

Windows 系統(tǒng)下可使用 set 命令設(shè)置環(huán)境變量:

(venv) $ set MAIL_USERNAME="ohmydog"
(venv) $ set MAIL_PASSWORD="ohmydog@sina.com"

在 Python shell 中發(fā)送電子郵件

你可以打開一個(gè) shell 會(huì)話, 發(fā)送一封測(cè)試郵件, 以檢查配置是否正確:

(venv) $ python hello.py shell
>>> from flask_mail import Message
>>> from hello import mail
>>> msg = Message('test subject', sender='xiaoxigu@sina.com', recipients=['baishe@sina.com'])
>>> msg.body = 'text body'
>>> msg.html = '<b>HTML</b> body'
>>> with app.app_context():
        mail.send(msg)

注意, Flask-Mail 中的 send() 函數(shù)使用 current_app, 因此要在激活的程序上下文中執(zhí)行。

在程序中集成發(fā)送電子郵件功能

為了避免每次都手動(dòng)編寫電子郵件消息, 我們最好把程序發(fā)送電子郵件的通用部分抽象出來(lái), 定義為一個(gè)函數(shù)。這樣, 還能使用 Jinja2 模板渲染郵件正文:

示例 6-3 hello.py:電子郵件支持

from flask_mail import Message

app.config['FLASK_MAIL_SUBJECT_PREFIX'] = '[Flasky]'
app.config['FLASK_MAIL_SENDER'] = 'Flasky Admin <flasky@sina.com>'

def send_email(to, subject, template, **kwargs):
    msg = Message(app.config['FLASK_MAIL_SUBJECT_PREFIX'] + subject, 
                  sender=app.config['FLASK_MAIL_SENDER'], recipients = [to])
    msg.body = render_template(template + '.txt', **kwargs)
    msg.html = render_template(template + '.html', **kwargs)
    mail.send(msg)

這個(gè)函數(shù)用到了兩個(gè)程序特定配置項(xiàng),分別定義郵件主題的前綴和發(fā)件人的地址。 send_email 函數(shù)的參數(shù)分別為收件人地址、主題、渲染郵件正文的模板和關(guān)鍵字參數(shù)列表。指定模板時(shí)不能包含擴(kuò)展名,這樣才能使用兩個(gè)模板分別渲染純文本正文和富文本正文。調(diào)用者將關(guān)鍵字參數(shù)傳給 render_template() 函數(shù),以便在模板中使用,進(jìn)而生成電子郵件正文。

index() 視圖函數(shù)很容易被擴(kuò)展,這樣每當(dāng)表單接收新名字時(shí),程序都會(huì)給管理員發(fā)送一封電子郵件。修改方法如示例 6-4 所示。

示例 6-4 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(usernme=form.name.data).first()
        if user is None:
            user = User(username=form.name.data)
            db.session.add(user)
            session['known'] = False
            if app.config['FLASKY_ADMIN']:
                send_email(app.config['FLASKY_ADMIN'], 'New User', 'mail/new_user', user=user)
            else:
                session['known'] = True
        session['name'] = form.name.data
        form.name.data = ''
        return redirect(url_for('index'))
    return render_template('index.html', form=form, name=session.get('name'), known=session.get('known', False))

電子郵件的收件人保存在環(huán)境變量 FLASKY_ADMIN 中,在程序啟動(dòng)過(guò)程中,它會(huì)加載到一個(gè)同名配置變量中。我們要?jiǎng)?chuàng)建兩個(gè)模板文件,分別用于渲染純文本和 HTML 版本的郵件正文。這兩個(gè)模板文件都保存在 templates 文件夾下的 mail 子文件夾中,以便和普通模板區(qū)分開來(lái)。電子郵件的模板中要有一個(gè)模板參數(shù)是用戶,因此調(diào)用 send_mail() 函數(shù)時(shí)要以關(guān)鍵字參數(shù)的形式傳入用戶。

除了前面提到的環(huán)境變量 MAIL_USERNAME 和 MAIL_PASSWORD 之外,這個(gè)版本的程序還需要使用環(huán)境變量 FLASKY_ADMIN 。Linux 和 Mac OS X 用戶可使用下面的命令添加:

(venv) $ export FLASKY_ADMIN=<your-email-address>

對(duì)微軟 Windows 用戶來(lái)說(shuō),等價(jià)的命令是:

(venv) $ set FLASKY_ADMIN=<Gmail username>

設(shè)置好這些環(huán)境變量后,我們就可以測(cè)試程序了。每次你在表單中填寫新名字時(shí),管理員都會(huì)收到一封電子郵件。

異步發(fā)送電子郵件

如果你發(fā)送了幾封測(cè)試郵件,可能會(huì)注意到 mail.send() 函數(shù)在發(fā)送電子郵件時(shí)停滯了幾秒鐘,在這個(gè)過(guò)程中瀏覽器就像無(wú)響應(yīng)一樣。為了避免處理請(qǐng)求過(guò)程中不必要的延遲,我們可以把發(fā)送電子郵件的函數(shù)移到后臺(tái)線程中。修改方法如示例 6-5 所示。

示例 6-5 hello.py:異步發(fā)送電子郵件

from threading import Thread

def send_async_email(app, mag):
    with app.app_context():
        mail.send(msg)
        
def send_email(to, subject, template, **kwargs):
    msg = Message(app.config['FLASK_MAIL_SUBJECT_PREFIX'] + subject, 
                  sender=app.config['FLASK_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

上述實(shí)現(xiàn)涉及一個(gè)有趣的問(wèn)題。很多 Flask 擴(kuò)展都假設(shè)已經(jīng)存在激活的程序上下文和請(qǐng)求上下文。Flask-Mail 中的 send() 函數(shù)使用 current_app ,因此必須激活程序上下文。不過(guò),在不同線程中執(zhí)行 mail.send() 函數(shù)時(shí),程序上下文要使用 app.app_context() 人工創(chuàng)建。

現(xiàn)在再運(yùn)行程序,你會(huì)發(fā)現(xiàn)程序流暢多了。不過(guò)要記住,程序要發(fā)送大量電子郵件時(shí),使用專門發(fā)送電子郵件的作業(yè)要比給每封郵件都新建一個(gè)線程更合適。例如,我們可以把執(zhí)行 send_async_email() 函數(shù)的操作發(fā)給 Celery 任務(wù)隊(duì)列。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容