電子郵件
在web程序中,經常會需要發送電子郵件。比如,在用戶注冊賬戶時發送確認郵件;定期向用戶發送熱門內容或是促銷信息等等。在Web程序中發送電子郵件並不復雜,借助擴展Flask-Mail或是第三方郵件服務,只需要幾行代碼就可以發送郵件。
下面例子中,我們使用一封示例郵件,郵件僅包含幾個必要的字段,如下:
標准的收信方和發信方字符串由姓名和郵箱地址兩部分組成,,二者由空格相隔,比如“姓名 <Email地址>”。字符串中的姓名是可選的,收信方一般可以不寫姓名,這時可以直接寫出郵箱地址,比如”hello@example.com”。
使用Flask-Mail發送電子郵件
擴展Flask-Mail包裝了python標准庫中的smtplib包,簡化了Flask程序中發送電子郵件的過程。我們使用pipenv安裝Flask-Mail:
(Lenovo-ezd1lI9Y) C:\Users\Lenovo>pipenv install flask-mail
Installing flask-mail...
Adding flask-mail to Pipfile's [packages]...
Installation Succeeded
和其他擴展類似,我們實例化Flask-Mail提供的Mail類並傳入程序實例以完成初始化,如下所示:
from flask_mail import Mail app = Flask(__name__) mail = Mail(app)
配置Flask-Mail
Flask_Mail通過鏈接SMTP(Simple Mail Transfer Protocol,簡單郵件傳輸協議)服務器來發送郵件。因此,在開始發送電子郵件前,我們需要配置SMTP服務器。如果電腦上已經設置了SMTP服務器,那么無需過多的配置即可使用,默認的郵件服務器配置即為localhost,端口為25.
在開發和測試階段,我們可以使用郵件服務提供商的SMTP服務器(比如Bmail),這時我們需要對Flask-Mail進行配置。下面列出了Flask-Mail提供的常用配置變量。
對發送的郵件進行加密可以避免郵件在發送過程中被第三方截獲和篡改。SSL(Security Socket Layer,安全套接字層)和TLS(Transport Layer Sceurity,傳輸層安全)是兩種常用的電子郵件安全協議。TLS繼承了SSL,並在SSL的基礎上做了一些改進(TLS是后期版本的SSL)。所以,在大多數情況下,名詞SSL和TLS可以互換使用。他們通過將MAIL_USE_SSL設置為True開啟。STARTTLS是另一種加密方式,它會對不安全的連接進行升級(使用SSL或TLS)。盡管它的名字中包含TLS,但也可能會使用SSL加密。根據加密的方式不同,端口也要相應改變,如下所示:
SSL/TLS加密
MAIL_USE_SSL = True
MAIL_PORT = 465
STARTTLS加密
MAIL_USE_TLS = True
MAIL_PORT = 587
當不對郵件進行加密時,郵件服務器的端口使用默認的25端口。
常用的電子郵箱服務提供商的SMTP配置信息如下所示:
163郵箱的SMTP服務器不支持STARTTLS,你需要使用SSL/TLS加密。集體來說就是將MAIL_USE_SSL設為True,MAIL_PORT設為465.
要使用這些郵箱服務,需要訪問對應的網站注冊一個賬戶。開啟郵箱的SMTP服務和獲取授權碼等操作均可以在個郵箱主頁-設置(-賬戶)中找到。
Gmail、Outlook、QQ郵箱等這類服務被稱為EPA(Email Service Provider),只適用於個人業務使用,不適合用來發送事務郵件(Transactional Email)。對於需要發送大量郵件的事務型郵件任務,更好的選擇是使用自己設置的SMTP服務器或是類似SendGrid、Mailgun的事務郵件服務提供商(Transactional Email Service),后邊會具體介紹。
在程序中,隨着配置逐漸增多,我們改用app.config對象的update()方法來加載配置:
app.py:郵件服務器配置
from flask import Flask from flask_mail import Mail app = Flask(__name__) app.config.update( MAIL_SERVER = os.getenv('MAIL_SERVER'), MAIL_PORT = 678, MAIL_USE_TLS = True, MAIL_USEERNAME = os.getenv('MAIL_USERNAME'), MAIL_PASSWORD = os.getenv('MAIL_PASSWORD'), MAIL_DEFAULT_SENDER = ('Grey Li', os.getenv('MAIL_USERNAME')) ) mail = Mail(app)
在實例化Mail類時,Flask-Mail會獲取配置以創建一個用於發信的對象,所以確保在實例化Mail類之前加載配置。
在我們的配置中,郵箱賬戶的密碼屬於敏感信息,不能直接寫在腳本中,所以設置為從系統變量中獲取。另外,在生產環境中,我們通常會使用不同的郵件服務器地址,所以這里也從環境變量中讀取。
你可以使用export/set命令設置環境變量,為了方便管理,我們把這些環境變量存儲在.env文件中:
MAIL_SERVER = 'smtp.qq.com' MAIL_USERNAME = '367224698@qq.com' MAIL_PASSWORD = 'ljfitqzfphlibjdj'
注意MAIL_PASSWORD是在qq郵箱的設置-賬戶中通過手機發送短信,獲取的授權碼,並不是qq的密碼,需要開發smtp服務
默認發信人由一個兩元素元祖組成,即(姓名,郵箱地址),比如:
MAIL_DEFAULT_SENDER = ('Your Name', 'your_name@example.com')
需要注意,使用郵件服務提供商提供的SMTP服務器發信時,發信人字符串中的郵件地址必須和郵箱地址相同,你可以直接只用MAIL_USERNAME的值構建發信人地址:
MAIL_DEFAULT_SENDER = ('Your Name', os.getenv('MAIL_USERNAME'))
Flask-Mail會把這個元祖轉換為標准的發信人格式,即Your Name <your_name@example.com>。 你可以直接以這種方式指定發信人,比如:
MAIL_DEFAULT_SENDER = 'Your Name <your_name@example.com>'
設置默認發信人后,在發信時就可以不用再指定發信人。
構建郵件數據
下面我們借助Python shell演示發送郵件的過程。郵件通過從Flask-Mail中導入的Message類表示,而發信功能通過我們在程序包的構造文件中創建的mail對象實現,我們先進性導入:
>>> from flask_mail import Message >>> from app import mail
一封郵件至少要包含主題、收件人、正文、發信人這幾個元素。發信人(sender)在前面我們已經使用MAIL_DEFAULT_SENDER配置變量指定過了,剩下的分別通過Message類的構造方法中的subject、recipients、body關鍵字傳入參數,其中recipients為一個包含電子郵件地址的列表。
message = Message(subject = 'title', recipients=['367224698@qq.com'], body='body')
加載環境變量方式:
1、flask項目可以通過.env加載環境變量,加載方式是運行 flask run或flask shell時加載
2、pipenv也可以通過.env加載環境變量,進入pipenv shell虛擬環境后,修改.env環境變量后再啟動flask app: flask run,flask還是會用原來的環境變量,原因是pipenv shell加載了環境變量並進行了緩存,然后flask加載環境變量時不會進行覆蓋。
此時可以退出pipenv shell然后再重新進入
和發信人字符串類似,收信人字符串可以為兩種形式:
‘Sam <sam@example.com>’或’sam@example.com’。
發送郵件
>>>mail.send(message)
完整的發送實例郵件的代碼如下:
>>> from flask_mail import Message >>> from app import mail >>> app.config['MAIL_PORT'] '465' >>> message = Message(subject = 'title', recipients=['367224698@qq.com'], body='body') >>> mail.send(message)
在實際操作時,報了錯誤:SMTPSenderRefused: (503, 'Error: need EHLO and AUTH first !', u'367224698@qq.com'),原因尚未搞清楚,配置都感覺ok,但還是報錯,懷疑是QQ郵箱服務器以為我的惡意的客戶端。。。
>>> from flask_mail import Message >>> from app import mail >>> app.config['MAIL_PORT'] '465' >>> message = Message(subject = 'title', recipients=['367224698@qq.com'], body='body') >>> mail.send(message) send: 'ehlo [172.20.10.4]\r\n' reply: '250-smtp.qq.com\r\n' reply: '250-PIPELINING\r\n' reply: '250-SIZE 73400320\r\n' reply: '250-AUTH LOGIN PLAIN\r\n' reply: '250-AUTH=LOGIN\r\n' reply: '250-MAILCOMPRESS\r\n' reply: '250 8BITMIME\r\n' reply: retcode (250); Msg: smtp.qq.com PIPELINING SIZE 73400320 AUTH LOGIN PLAIN AUTH=LOGIN MAILCOMPRESS 8BITMIME send: u'mail FROM:<367224698@qq.com> size=263\r\n' reply: '503 Error: need EHLO and AUTH first !\r\n' reply: retcode (503); Msg: Error: need EHLO and AUTH first ! send: 'rset\r\n' reply: '250 Ok\r\n' reply: retcode (250); Msg: Ok send: 'quit\r\n' reply: '221 Bye\r\n' reply: retcode (221); Msg: Bye Traceback (most recent call last): File "<console>", line 1, in <module> File "c:\users\lenovo\.virtualenvs\lenovo-ezd1li9y\lib\site-packages\flask_mail.py", line 492, in send message.send(connection) File "c:\users\lenovo\.virtualenvs\lenovo-ezd1li9y\lib\site-packages\flask_mail.py", line 427, in send connection.send(self) File "c:\users\lenovo\.virtualenvs\lenovo-ezd1li9y\lib\site-packages\flask_mail.py", line 192, in send message.rcpt_options) File "c:\python27\Lib\smtplib.py", line 737, in sendmail raise SMTPSenderRefused(code, resp, from_addr) SMTPSenderRefused: (503, 'Error: need EHLO and AUTH first !', u'367224698@qq.com')
flask_mail發送163郵件
下面用163郵箱試一下
先進入163郵件開啟smtp服務,獲取授權碼
授權碼就是客戶端登錄163郵箱時的密碼,用戶名就是郵箱地址
獲取授權碼后,記錄下來
在.env文件中設置郵箱信息,在命令行中運行flask shell時,會自動到該文件中獲取環境變量
email\.env:
FLASK_ENV=development MAIL_SERVER = 'smtp.163.com' MAIL_USERNAME = 'xiaxiaoxu1987@163.com' MAIL_PASSWORD = 'FOREVER022941' email\app.py: from flask import Flask from flask_mail import Mail, Message import os app = Flask(__name__) app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True #設置app.config信息,重點是用戶名和密碼 app.config.update( SECRET_KEY = "SECRET KEY", MAIL_SERVER = os.getenv('MAIL_SERVER'), MAIL_PORT = 465, #MAIL_PORT = 587, #MAIL_USE_TLS = True, MAIL_USE_SSL = True, MAIL_USERNAME = os.getenv('MAIL_USERNAME'), MAIL_PASSWORD = os.getenv('MAIL_PASSWORD'), MAIL_DEFAULT_SENDER = (os.getenv('MAIL_USERNAME')) )
命令行界面,操作發送郵件
>>> from flask_mail import Message >>> from app import mail >>> msg = Message('text', sender = 'xiaxiaoxu1987@163.com', recipients=['367224698@qq.com']) >>> msg.body = 'body' >>> mail.send(msg) send: 'ehlo [172.20.10.4]\r\n' reply: '250-mail\r\n' reply: '250-PIPELINING\r\n' reply: '250-AUTH LOGIN PLAIN\r\n' reply: '250-AUTH=LOGIN PLAIN\r\n' reply: '250-coremail 1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UrHJsU2UCa0xDrUUUUj\r\n' reply: '250-STARTTLS\r\n' reply: '250 8BITMIME\r\n' reply: retcode (250); Msg: mail PIPELINING AUTH LOGIN PLAIN AUTH=LOGIN PLAIN coremail 1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UrHJsU2UCa0xDrUUUUj STARTTLS 8BITMIME send: 'AUTH PLAIN AHhpYXhpYW94dTE5ODdAMTYzLmNvbQBGT1JFVkVSMDIyOTQx\r\n' reply: '235 Authentication successful\r\n' reply: retcode (235); Msg: Authentication successful send: u'mail FROM:<xiaxiaoxu1987@163.com>\r\n' reply: '250 Mail OK\r\n' reply: retcode (250); Msg: Mail OK send: u'rcpt TO:<367224698@qq.com>\r\n' reply: '250 Mail OK\r\n' reply: retcode (250); Msg: Mail OK send: 'data\r\n' reply: '354 End data with <CR><LF>.<CR><LF>\r\n' reply: retcode (354); Msg: End data with <CR><LF>.<CR><LF> data: (354, 'End data with <CR><LF>.<CR><LF>') send: 'Content-Type: text/plain; charset="utf-8"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 7bit\r\nSubject: text\r\nFrom: xiaxiaoxu1987@163.com\r\nTo: 367224698@qq.com\r\nDate: Wed, 10 Apr 2019 21:13:57 +0800\r\nMessage-ID: <155490203514.14300.10708927723623278864@DESKTOP-F82U4NJ>\r\n\r\nbody\r\n.\r\n' reply: '250 Mail OK queued as smtp11,D8CowACXrmER7K1cTRnVFA--.36353S2 1554902035\r\n' reply: retcode (250); Msg: Mail OK queued as smtp11,D8CowACXrmER7K1cTRnVFA--.36353S2 1554902035 data: (250, 'Mail OK queued as smtp11,D8CowACXrmER7K1cTRnVFA--.36353S2 1554902035') send: 'quit\r\n' reply: '221 Bye\r\n' reply: retcode (221); Msg: Bye
查看郵箱發送結果:
為了方便重用,我們把這些代碼包裝成一個通用的發信函數send_mail(),如下所示:
app.py: 通用發信函數
from flask import Flask from flask_mail import Mail, Message import os app = Flask(__name__) app.config.update( MAIL_SERVER = os.getenv('MAIL_SERVER'), MAIL_PORT = '465', #MAIL_USE_TLS = True, MAIL_USE_SSL = True, MAIL_USEERNAME = os.getenv('MAIL_USERNAME'), MAIL_PASSWORD = os.getenv('MAIL_PASSWORD'), MAIL_DEFAULT_SENDER = (os.getenv('MAIL_USERNAME')) ) mail = Mail(app) def send_mail(subject, to, body): message = Message(subject, recipients = [to], body = body) mail.send(message)
假設我們的程序時一個周刊訂閱程序,當用戶在表單中填寫了正確的email地址時,我們就發送一封郵件來通知用戶訂閱成功。通過在index視圖中調用send_email()即可發送郵件,如下所示: 在視圖函數中發送郵件
@app.route('/subscribe', methods = ['GET', 'POST']) def subscribe(): form = SubscribeForm() if form.validate_on_submit(): name = form.name.data email = form.email.data send_subscribe_mail('Subscribe Success!', email, name = name) flash('Confirmation email have been sent! Check your inbox.') return redirect(url_for('subscribe')) return render_template('subscribe.html', form = form)