Tutorial on sending and receiving emails under the Python Flask framework, pythonflask
Brief Introduction
In most of these tutorials, we will spare no effort to introduce how to use databases. Today, we will focus on another important feature in web applications, that is, how to push emails to users.
In a lightweight application, we may add the following mail service function: when a user has a new fan, we send an email to notify the user. There are many ways to implement this feature, and we hope to provide a reusable general framework for processing.
Introduction to Flask-Mail
Fortunately for us, there are already many external plug-ins to process emails. Although we can't handle emails exactly as we thought, it's quite close.
Installing Flask-Mail in a virtual environment is quite simple. Users other than Windows can use the following command to install:
flask/bin/pip install flask-mail
Windows user installation is slightly different, because some modules used by Flask-Mail cannot run on Windows systems. You can use the following command:
flask\Scripts\pip install --no-deps lamson chardet flask-mail
Configuration:
Looking back at the unit test cases in the previous article, we have added the configuration to support the function of notifying us by email when a test of a certain version of the application fails. This example shows how to configure mail support.
Remind everyone that we need to set two aspects:
- Email server information
- User email address
The following are the configurations used in the previous article.
# email serverMAIL_SERVER = 'your.mailserver.com'MAIL_PORT = 25MAIL_USE_TLS = FalseMAIL_USE_SSL = FalseMAIL_USERNAME = 'you'MAIL_PASSWORD = 'your-password' # administrator listADMINS = ['you@example.com']
No email server or mailbox is available. Now let's take an example to see how to use a gmail account to send Emails:
# email serverMAIL_SERVER = 'smtp.googlemail.com'MAIL_PORT = 465MAIL_USE_TLS = FalseMAIL_USE_SSL = TrueMAIL_USERNAME = 'your-gmail-username'MAIL_PASSWORD = 'your-gmail-password' # administrator listADMINS = ['your-gmail-username@gmail.com']
In addition, we can initialize a Mail object to connect to the SMTP Mail server and send the Mail:
from flask.ext.mail import Mailmail = Mail(app)
Try sending an email!
To learn how flask-mail works, we can send an email from the command line. Go to python shell and execute the following script:
>>> from flask.ext.mail import Message>>> from app import mail>>> from config import ADMINS>>> msg = Message('test subject', sender = ADMINS[0], recipients = ADMINS)>>> msg.body = 'text body'>>> msg.html = '<b>HTML</b> body'>>> mail.send(msg)
The above Code uses the first mailbox as the sender to send an email to all the mailboxes according to the list of email addresses configured in inconfig. py. The email content is displayed in two formats: Text and html, and you can see which format depends on your mail client.
How simple and small! You can integrate it into your application now.
Email framework
Now we can write a help function to send emails. This is a general version of the above test. We put this function into a new original file for mail support (fileapp/emails. py ):
from flask.ext.mail import Messagefrom app import mail def send_email(subject, sender, recipients, text_body, html_body): msg = Message(subject, sender, recipients) msg.body = text_body msg.html = html_body mail.send(msg)
The Mail support for Flask-Mail is beyond our current range of use. features such as BCC and attachments will not be used in this application.
Follower reminder
Now that we have the basic framework for sending emails, we can write the function for sending the follower reminder (fileapp/emails. py ):
from flask import render_templatefrom config import ADMINS def follower_notification(followed, follower): send_email("[microblog] %s is now following you!" % follower.nickname, ADMINS[0], [followed.email], render_template("follower_email.txt", user = followed, follower = follower), render_template("follower_email.html", user = followed, follower = follower))
Have you found any surprises here? Our old friend render_template function once appeared.
If you still remember, we use this function to render templates in views. Just as it is not good to write html in views, using mail templates is an ideal choice. We may want to separate the logic and performance, so the email template will be put together with other attempt templates in the template folder.
Therefore, we need to write plain text and webpage mail templates for follower reminders. below is the plain text version (fileapp/templates/follower_email.txt ):
Dear {{user.nickname}}, {{follower.nickname}} is now a follower. Click on the following link to visit {{follower.nickname}}'s profile page: {{url_for("user", nickname = follower.nickname, _external = True)}} Regards, The microblog admin
The following is a webpage-based email with better results (fileapp/templates/follower_email.html ):
<p>Dear {{user.nickname}},</p><p><a href="{{url_for("user", nickname = follower.nickname, _external = True)}}">{{follower.nickname}}</a> is now a follower.</p><table> <tr valign="top"> <td></td> <td> <a href="{{url_for('user', nickname = follower.nickname, _external = True)}}">{{follower.nickname}}</a><br /> {{follower.about_me}} </td> </tr></table><p>Regards,</p><p>The <code>microblog</code> admin</p>
Note: The parameter _ external = True of the url_for function in the template. By default, the url generated by the url_for function is relative to our domain name. For example, the returned value of the url_for ("index") function is/index. However, when sending an email, we want a url such as http: // localhost: 5000/index, which has no domain context in the email, therefore, we must generate a url with a domain name, And _ external argument does this.
The last step is to process the "follow" process, that is, the view function for trigger mail notifications (fileapp/views. py ):
from emails import follower_notification @app.route('/follow/<nickname>')@login_requireddef follow(nickname): user = User.query.filter_by(nickname = nickname).first() # ... follower_notification(user, g.user) return redirect(url_for('user', nickname = nickname))
Now you can create two users (if there are no users) and try to show one user to another to understand how email reminders work.
Is that true? Are we done?
We may be excited to complete this task and delete the email reminder function from the unfinished list.
However, if you test the application now, you will find that when you click the follow link, the page will respond in 2 to 3 seconds, and the browser will refresh the page, this is not available before.
What happened?
The problem is that Flask-Mail uses synchronous mode to send emails. Starting from sending an email until the email is delivered, the Web server is blocked throughout the process. If we try to send an email to a server slowly or even worse, it is temporarily offline. Can you imagine what will happen? Very bad.
This is a terrible restriction. Sending emails should be background tasks without interfering with the Web server. Let's see how we can solve this problem.
Execute asynchronous calls in Python
We want the send_email function to return immediately after sending the mail. We need to move the mail to the background process for asynchronous execution.
In fact, python already supports asynchronous tasks, but in fact, you can also use other methods, such as threads and multi-process modules, to Implement Asynchronous tasks.
Every time we need to send an email, start a thread to process it, saving resources than starting a new process. Therefore, let's put the mail. send (msg) call into another thread. (Fileapp/emails. py ):
from threading import Thread def send_async_email(msg): mail.send(msg) def send_email(subject, sender, recipients, text_body, html_body): msg = Message(subject, sender = sender, recipients = recipients) msg.body = text_body msg.html = html_body thr = threading.Thread(target = send_async_email, args = [msg]) thr.start()
If you test the 'follow' function, you will now find that the browser will refresh before sending the email.
Therefore, we have implemented asynchronous transmission. However, if we need other asynchronous functions in the future, do we still need to implement it again?
The process is the same, so there will be repeated code in every situation, this is very bad.
We can improve the Code through decorator. The code for using the decorator is as follows:
from decorators import async @asyncdef send_async_email(msg): mail.send(msg) def send_email(subject, sender, recipients, text_body, html_body): msg = Message(subject, sender = sender, recipients = recipients) msg.body = text_body msg.html = html_body send_async_email(msg)
Better, right?
The code to implement this method is actually very simple. Create a new source file (fileapp/decorators. py ):
from threading import Thread def async(f): def wrapper(*args, **kwargs): thr = Thread(target = f, args = args, kwargs = kwargs) thr.start() return wrapper
Now we have created a useful framework for asynchronous tasks. We can say it has been done!
Just as an exercise, let's think about why this method seems to use processes rather than threads. We don't want a process to be started whenever we need to send an email, so we can use thePoolclass instead of themultiprocessingmodule. This class will create a specified number of processes (these are the sub-processes of the main process), and these sub-processes will be sent to the process pool through theapply_asyncmethod, waiting to accept the task to work. This may be an interesting way for a busy website, but we will still maintain the current thread mode.
Last words
The source code of this updatedmicroblogapplication is as follows:
Download the microblog-0.11.zip.