# Flask實作_ext_09_Flask-Mail_郵件發送
###### tags: `flask` `flask_ext` `python` `mail`
:::danger
官方文件:
* [Flask-Mail](https://pythonhosted.org/Flask-Mail/)
參考:
* [Flask Web開發-非同步郵件寄送](https://github.com/miguelgrinberg/flasky/blob/master/app/email.py)
:::
## 說明
`flask-mail`本身是將`smtplib`包裝起來的一個套件,這部份可以從`flask-mail.py`看的出來,搭配Flask應用對於我們派送註冊驗證信或警示訊息傳遞都非常實用。
![](https://i.imgur.com/xEy7tkY.png)
## 安裝
```shell=
pip install flask-mail
```
## 範例
import的模式跟其它擴展一樣都有兩種方式,實作的時候放入`app`,或是利用`init_app`,視你的使用情境來選用,如下兩個範例:
```python=
# 以此方式,所有的郵件設置會一次性渲染
from flask import Flask
from flask-mail import Mail
app = Flask(__name__)
mail = Mail(app)
```
```python=
# 如果你有不同專案要走不同郵件設置的話,用此方式較佳
# 如果你使用工廠模式的話,也會以此方式來做初始化
from flask import Flask
from flask-mail import Mail
mail = Mail()
app = Flask(__name__)
mail.init_app(app)
```
如果你有使用`github`的習慣也請注意,`flask-mail`的參數設置或許會有隱密性資料,請記得使用環境變數來設置,如下<sub>(Windows範例)</sub>:
```shell
set MAIL_USERNAME=<YOUR MAIL COUNT>
set MAIL_PASSWORD=<YOUR MAIL PASSWORD>
```
設置之後,就可以利用`os.environ.get`來取得環境變數,如此即可避免敏感資訊置於公開場合
```python
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
```
### 範例:flask-mail 寄一封信
這次的範例使用的是`hotmail`做SMTP Server,如果你使用的是公司內部的Mail Server或是Gmail,再依實際情況調整設置即可,如下範例:
```python=
# file name:test_flask-mail.py
from flask import Flask
from flask_mail import Mail
from flask_mail import Message
app = Flask(__name__)
app.config.update(
# hotmail的設置
MAIL_SERVER='smtp.live.com',
MAIL_PROT=587,
MAIL_USE_TLS=True,
MAIL_USERNAME='Your Hotmail Count',
MAIL_PASSWORD='Your Hotmail Password'
)
# 記得先設置參數再做實作mail
mail = Mail(app)
@app.route("/message")
def index():
# 主旨
msg_title = 'Hello It is Flask-Mail'
# 寄件者,若參數有設置就不需再另外設置
msg_sender = 'Sender Mail@mail_domain.com'
# 收件者,格式為list,否則報錯
msg_recipients = ['Recipients@mail_domain.com']
# 郵件內容
msg_body = 'Hey, I am mail body!'
# 也可以使用html
# msg_html = '<h1>Hey,Flask-mail Can Use HTML</h1>'
msg = Message(msg_title,
sender=msg_sender,
recipients=msg_recipients)
msg.body = msg_body
# msg.html = msg_html
# mail.send:寄出郵件
mail.send(msg)
return 'You Send Mail by Flask-Mail Success!!'
if __name__ == "__main__":
app.debug = True
app.run()
```
相關的說明皆寫於註解,設置完畢之後就可以執行專案測試,輸入網址`http://127.0.0.1:5000/message`,得到訊息如下:
![](https://i.imgur.com/s5pnyft.png)
現在可以開啟郵箱來確認是否收到信件,如下:
![](https://i.imgur.com/uBIn1mT.png)
在`flask-mail`中有三個class分別為`Mail`,`Message`,`Connection`,這部份最好的理解方式就是看一次[官方文件](https://pythonhosted.org/Flask-Mail/)說明。
`Message`是實作郵件內容的類別,除了可以以文字做內容之外,也可以利用Html,這代表我們可以利用`jinja2`的樣板來做渲染,也可以透過`attach`實作夾帶附件。<sub>(下面有簡單說明)</sub>
另外,不同於操作`smtplib`需要做`connect`,在實作`Mail.send`的時候`flask-mail`會幫我們處理這部份的作業。
### 非同步寄送郵件
**參考來源請見文章開頭連結**
使用非同步的主因在於不希望使用者在按下發送信件按鈕之後需要浪費時間等待寄送信件的程序,這個等待時間會造成使用者體驗不佳,而且使用者也不需要浪費時間等待,因此利用多執行緒將使用者點擊按鈕之後的信件派送程序切割,如下範例:
```python=
# file name:test_flask-mail.py
from flask import Flask
from flask_mail import Mail
from flask_mail import Message
# 加入threating
from threading import Thread
app = Flask(__name__)
app.config.update(
# hotmail的設置
MAIL_SERVER='smtp.live.com',
MAIL_PROT=587,
MAIL_USE_TLS=True,
MAIL_USERNAME='Your Hotmail Count',
MAIL_PASSWORD='Your Hotmail Password'
)
# 記得先設置參數再做實作mail
mail = Mail(app)
@app.route("/message")
def index():
# 主旨
msg_title = 'Hello It is Flask-Mail'
# 寄件者,若參數有設置就不需再另外設置
msg_sender = 'Sender Mail@mail_domain.com'
# 收件者,格式為list,否則報錯
msg_recipients = ['Recipients@mail_domain.com']
# 郵件內容
# msg_body = 'Hey, I am mail body!'
# 也可以利用html做內容
msg_html = '<h1>Hey,Flask-mail Can Use HTML, and I Use thread</h1>'
msg = Message(msg_title,
sender=msg_sender,
recipients=msg_recipients)
# msg.body = msg_body
msg.html = msg_html
# 使用多線程
thr = Thread(target=send_async_email, args=[app, msg])
thr.start()
return 'You Send Mail by Flask-Mail Success!!'
def send_async_email(app, msg):
# 下面有說明
with app.app_context():
mail.send(msg)
if __name__ == "__main__":
app.debug = True
app.run()
```
第44行:你必需讓程序是在相同的Context內,因此需要<sub>也必需</sub>利用`app.app_context`來確保線程。
這個範例我用Html做郵件內容的渲染,如下:
![](https://i.imgur.com/LDdlGEf.png)
`threading`的部份不在此節的範圍,不多說,我們看`send_async_email`這個function。我們取得了`app`跟`msg`兩個物件之後,利用了`app.app_context`來讓整個程序是在一個程序的上下文內,這是`flask-mail`中的`send`的限制。
雖然兩個物件都是從`context`中傳來的,但是因為我們開了另一個新的`thread`,所以必需手動將物件放入一個程序上下文中。
調整為非同步模式,執行專案會發現流暢許多,網頁不會有停頓的感覺,整個使用者的體驗是可以有效提高的,因為使用者不需要再等待信件派送的程序。
如果你拿掉了`with app.app_context`的話,會有以下錯誤說明:
```
This typically means that you attempted to use functionality that needed
to interface with the current application object in a way. To solve
this set up an application context with app.app_context(). See the
documentation for more information.
```
## 總結
這一話我們討論了`flask-mail`的應用以及非同步對使用者體驗的改善,在非同步的部份如果相進一步的瞭解,會建議可以搭配`Celery`來使用,值得學習,另外我們將測試使用`jinja2`來實作版面,不要忘了繼續學習。
**Flask-Mail_利用模板渲染:**[Flask實作_ext_10_Flask-Mail_利用模板渲染](https://hackmd.io/s/BkYRYDmBf)
## 參數
### 配置說明
部份配合原始碼看一下比較能夠理解,如下圖說明:
![](https://i.imgur.com/aUSM5Cj.png)
* MAIL_SERVER : 預設為localhost
SMTP的主機位置
* MAIL_PORT : 預設為 25
SMTP的PORT
* MAIL_USE_TLS : 預設為 False
是否使用TLS
* MAIL_USE_SSL : 預設為 False
是否使用SSL
* MAIL_DEBUG : 預設為 app.debug
是否啟用DEBUG模式
預設會使用flask的debug設置
* MAIL_USERNAME : 預設為 None
登入mail server的帳號
* MAIL_PASSWORD : 預設為 None
登入mail server的密碼
* MAIL_DEFAULT_SENDER : 預設為 None
預設郵件寄件人,設置之後,在實作message的時候若沒有設置就以此為主。
* MAIL_MAX_EMAILS : 預設為 None
一次連接的郵件最大發送數,不過基本上大量郵件的話會採不同的方式來處理。
像多線程或丟另一個程序處理,否則會造成發送郵件時卡住。
* MAIL_SUPPRESS_SEND : 預設為 app.testing
設置True的時候可以限制發送郵件
在開發測試的時候,實作send()時就不會真的發送郵件。
* MAIL_ASCII_ATTACHMENTS : 預設為 False
將編碼格式轉為ASCII
### Message
```python=
class flask_mail.Message(subject='', recipients=None, body=None,
html=None, sender=None, cc=None, bcc=None,
attachments=None, reply_to=None, date=None,
charset=None, extra_headers=None,
mail_options=None, rcpt_options=None)
```
* Parameters:
* subject – email subject header...郵件主旨
* recipients – list of email addresses...收件人,格式需為list,可利用add_recipient加入
* body – plain text message...郵件文字內容
* html – HTML message...郵件html內容
* sender – email sender address, or MAIL_DEFAULT_SENDER by default...寄件人
* cc – CC list...副件人員,格式需為list
* bcc – BCC list...密件,格式需為list
* attachments – list of Attachment instances...附件,可利用attach()加入
* reply_to – reply-to address...待測
* date – send date...寄件日期
* charset – message character set...郵件編碼
* extra_headers – A dictionary of additional headers for the message...待測
* mail_options – A list of ESMTP options to be used in MAIL FROM command...待測
* rcpt_options – A list of ESMTP options to be used in RCPT commands...待測
* method:
* add_recipient(recipient):增加收件人
```python=
self.recipients.append(recipient)
```
* attach(filename=None, content_type=None, data=None, disposition=None, headers=None):增加附件
```python=
self.attachments.append(
Attachment(filename, content_type, data, disposition, headers))
```
## 常用配置
### hotmail
```
MAIL_SERVER='smtp.live.com',
MAIL_PROT=587,
MAIL_USE_TLS=True,
MAIL_USERNAME='Your Hotmail Count',
MAIL_PASSWORD='Your Hotmail Password'
```
### gmail
使用gmail記得要先申請低安全性應用程式的密碼,可以產生一組跟你的密碼不同的密碼,不過如果你有申請兩段式登入認證的話,就不能用了!
```
MAIL_SERVER='smtp.gmail.com',
MAIL_PROT=465,
MAIL_USE_SSL=True,
MAIL_USERNAME='Your Gmail Count',
MAIL_PASSWORD='Your Gmail Password'
```
### 其它smtp測試
如果覺得使用其它smtp測試很麻煩的話,可以試著自己建置一個測試用的smtp。
只是個人透過此方法沒有成功過,我另外寫一個利用smtplib直接寄可以成功,利用telnet去測也正常,但是透過flask-mail一直出現錯誤訊息(目標電腦拒絕連線 10061),如果有成功的前輩再指導。
```
import smtpd
import asyncore
server = smtpd.DebuggingServer(('127.0.0.1', 1025), None)
asyncore.loop()
```