簡介
給使用者傳送郵件是 Web 應用中最常見的任務之一,比如使用者註冊,找回密碼等。Python 內建了一個 smtplib 的模組,可以用來傳送郵件,這裡我們使用 Flask-Mail,是因為它可以和 Flask 整合,讓我們更方便地實現此功能。
安裝
使用 pip
安裝:
1 |
$ pip install Flask-Mail |
或下載原始碼安裝:
1 2 3 |
$ git clone https://github.com/mattupstate/flask-mail.git $ cd flask-mail $ python setup.py install |
傳送郵件
Flask-Mail 連線到簡單郵件傳輸協議 (Simple Mail Transfer Protocol, SMTP) 伺服器,並把郵件交給這個伺服器傳送。這裡以 QQ
郵箱為例,介紹如何簡單地傳送郵件。在此之前,我們需要知道 QQ
郵箱的伺服器地址和埠是什麼,點此檢視。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# -*- 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' # 郵件伺服器地址 app.config['MAIL_PORT'] = 25 # 郵件伺服器埠 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) |
在傳送前,需要先設定使用者名稱和密碼,當然你也可以直接寫在檔案裡,如果是從環境變數讀取,可以這麼做:
1 2 |
$ export MAIL_USERNAME='me@example.com' $ export MAIL_PASSWORD='123456' |
將上面的 sender
和 recipients
改一下,就可以進行測試了。
從上面的程式碼,我們可以知道,使用 Flask-Mail 傳送郵件主要有以下幾個步驟:
- 配置 app 物件的郵件伺服器地址,埠,使用者名稱和密碼等
- 建立一個 Mail 的例項:
mail = Mail(app)
- 建立一個 Message 訊息例項,有三個引數:郵件標題、傳送者和接收者
- 建立郵件內容,如果是 HTML 格式,則使用
msg.html
,如果是純文字格式,則使用msg.body
- 最後呼叫
mail.send(msg)
傳送訊息
Flask-Mail 配置項
Flask-Mail 使用標準的 Flask 配置 API 進行配置,下面是一些常用的配置項:
配置項 | 說明 |
---|---|
MAIL_SERVER | 郵件伺服器地址,預設為 localhost |
MAIL_PORT | 郵件伺服器埠,預設為 25 |
MAIL_USE_TLS | 是否啟用傳輸層安全 (Transport Layer Security, TLS)協議,預設為 False |
MAIL_USE_SSL | 是否啟用安全套接層 (Secure Sockets Layer, SSL)協議,預設為 False |
MAIL_DEBUG | 是否開啟 DEBUG,預設為 app.debug |
MAIL_USERNAME | 郵件伺服器使用者名稱,預設為 None |
MAIL_PASSWORD | 郵件伺服器密碼,預設為 None |
MAIL_DEFAULT_SENDER | 郵件發件人,預設為 None,也可在 Message 物件裡指定 |
MAIL_MAX_EMAILS | 郵件批量傳送個數上限,預設為 None |
MAIL_SUPPRESS_SEND | 預設為 app.testing,如果為 True,則不會真的傳送郵件,供測試用 |
非同步傳送郵件
使用上面的方式傳送郵件,會發現頁面卡頓了幾秒才出現訊息,這是因為我們使用了同步的方式。為了避免傳送郵件過程中出現的延遲,我們把傳送郵件的任務移到後臺執行緒中,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# -*- 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) |
在上面,我們建立了一個執行緒,執行的任務是send_async_email
,該任務的實現涉及一個問題1:
很多 Flask 擴充套件都假設已經存在啟用的程式上下文和請求上下文。Flask-Mail 中的
send()
函式使用current_app
,因此必須啟用程式上下文。不過,在不同執行緒中執行mail.send()
函式時,程式上下文要使用app.app_context()
人工建立。
帶附件的郵件
有時候,我們發郵件的時候需要新增附件,比如文件和圖片等,這也很簡單,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# -*- 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' # 郵件伺服器地址 app.config['MAIL_PORT'] = 25 # 郵件伺服器埠 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)
開啟了本機的某張圖片,然後通過msg.attach()
方法將附件內容新增到 Message 物件。msg.attach()
方法的第一個引數是附件的檔名,第二個引數是檔案內容的 MIME (Multipurpose Internet Mail Extensions)
型別,第三個引數是檔案內容。
如果你不知道附件的 MIME
型別是什麼,可以檢視 MIME 參考手冊。
批量傳送
在某些情況下,我們需要批量傳送郵件,比如給網站的所有註冊使用者傳送改密碼的郵件,這時為了避免每次發郵件時都要建立和關閉跟伺服器的連線,我們的程式碼需要做一些調整,類似如下:
1 2 3 4 5 |
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) |
上面的工作方式,使得應用與電子郵件伺服器保持連線,一直到所有郵件已經傳送完畢。某些郵件伺服器會限制一次連線中的傳送郵件的上限,這樣的話,你可以配置 MAIL_MAX_EMAILS
。
需要注意的是,更好的傳送大量電子郵件的方式是用專門的作業系統,比如用 Celery 任務佇列等。
本文完整的程式碼在這裡。