第六章 电子邮件

很多类型的应用程序都需要在特定事件发生时(例如注册)提醒用户, 而常用的通信方法是电子邮件。

使用 Flask-Mail 提供电子邮件支持

pip install flask-mail

Flask-Mail SMTP服务器的配置

配  置 默认值 说  明
MAIL_SERVER localhost 电子邮件服务器的主机名或 IP 地址
MAIL_PORT 25 电子邮件服务器的端口
MAIL_USE_TLS False 启用传输层安全(Transport Layer Security,TLS)协议
MAIL_USE_SSL False 启用安全套接层(Secure Sockets Layer,SSL)协议
MAIL_USERNAME None 邮件账户的用户名
MAIL_PASSWORD None 邮件账户的密码

在开发过程中, 如果连接到外部 SMTP 服务器, 则可能更方便。下面使用新浪邮箱账户发送电子邮件:

示例 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')

千万不要把账户密码直接写入脚本, 特别是你计划开源自己的作品时。为了保护账户信息, 你需要让脚本从环境中导入敏感信息。

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

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

from flask_mail import Mail
mail = Mail(app)

保存电子邮件服务器用户名和密码的两个环境变量要在环境中定义。如果你在 Linux 或 Mac OS X 中使用 bash, 则这样设置这两个变量:

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

Windows 系统下可使用 set 命令设置环境变量:

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

在 Python shell 中发送电子邮件

你可以打开一个 shell 会话, 发送一封测试邮件, 以检查配置是否正确:

(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() 函数使用 current_app, 因此要在激活的程序上下文中执行。

在程序中集成发送电子邮件功能

为了避免每次都手动编写电子邮件消息, 我们最好把程序发送电子邮件的通用部分抽象出来, 定义为一个函数。这样, 还能使用 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)

这个函数用到了两个程序特定配置项,分别定义邮件主题的前缀和发件人的地址。 send_email 函数的参数分别为收件人地址、主题、渲染邮件正文的模板和关键字参数列表。指定模板时不能包含扩展名,这样才能使用两个模板分别渲染纯文本正文和富文本正文。调用者将关键字参数传给 render_template() 函数,以便在模板中使用,进而生成电子邮件正文。

index() 视图函数很容易被扩展,这样每当表单接收新名字时,程序都会给管理员发送一封电子邮件。修改方法如示例 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))

电子邮件的收件人保存在环境变量 FLASKY_ADMIN 中,在程序启动过程中,它会加载到一个同名配置变量中。我们要创建两个模板文件,分别用于渲染纯文本和 HTML 版本的邮件正文。这两个模板文件都保存在 templates 文件夹下的 mail 子文件夹中,以便和普通模板区分开来。电子邮件的模板中要有一个模板参数是用户,因此调用 send_mail() 函数时要以关键字参数的形式传入用户。

除了前面提到的环境变量 MAIL_USERNAME 和 MAIL_PASSWORD 之外,这个版本的程序还需要使用环境变量 FLASKY_ADMIN 。Linux 和 Mac OS X 用户可使用下面的命令添加:

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

对微软 Windows 用户来说,等价的命令是:

(venv) $ set FLASKY_ADMIN=<Gmail username>

设置好这些环境变量后,我们就可以测试程序了。每次你在表单中填写新名字时,管理员都会收到一封电子邮件。

异步发送电子邮件

如果你发送了几封测试邮件,可能会注意到 mail.send() 函数在发送电子邮件时停滞了几秒钟,在这个过程中浏览器就像无响应一样。为了避免处理请求过程中不必要的延迟,我们可以把发送电子邮件的函数移到后台线程中。修改方法如示例 6-5 所示。

示例 6-5 hello.py:异步发送电子邮件

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

上述实现涉及一个有趣的问题。很多 Flask 扩展都假设已经存在激活的程序上下文和请求上下文。Flask-Mail 中的 send() 函数使用 current_app ,因此必须激活程序上下文。不过,在不同线程中执行 mail.send() 函数时,程序上下文要使用 app.app_context() 人工创建。

现在再运行程序,你会发现程序流畅多了。不过要记住,程序要发送大量电子邮件时,使用专门发送电子邮件的作业要比给每封邮件都新建一个线程更合适。例如,我们可以把执行 send_async_email() 函数的操作发给 Celery 任务队列。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容