专注细节
努力进步

Flask-Email Support

Configuration This is what we have done in the previous article:

# email server
MAIL_SERVER = 'your.mailserver.com'
MAIL_PORT = 25
MAIL_USERNAME = None
MAIL_PASSWORD = None

# administrator list
ADMINS = ['you@example.com']
 If you want the application to send emails via your gmail account you would just enter the following: 

# email server
MAIL_SERVER = 'smtp.googlemail.com'
MAIL_PORT = 465
MAIL_USE_TLS = False
MAIL_USE_SSL = True
MAIL_USERNAME =  )
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')

# administrator list
ADMINS = ['your-gmail-username@gmail.com']
 Note that the username and password are read from environment varibales.

You will need to set MAIL_USERNAME and MAIL_PASSWORD to your Gmail login credentials. Putting sensitive information in environment variables is safer than writing down the information on a source file. We also need to init a Mail object, as this will be the object that will connect to the SMTP server and send the emails for us:
from flask.ext.mail import Mail
mail = Mail(app)

Let’s send an email!

from flask.ext.mail import Message
from app import mail,app
from config import ADMINS
msg = Message('test subject', sender = ADMINS[0], recipients = ADMINS)
msg.body = 'text body'
msg.html = '<b>HTML</b> body'
with app.app_context():
    mail.send(msg)
 The snippet of code above will send an email to the list of admins that are configured in config.py. The sender will be the first admin in the list. The email will have text and HTML versions, so depending on how your email client is setup you may see one or the other. Note that we needed to create an app_context to send the email. Now, it's time to integrate this code into our application: 

A simple email framework

from flask.ext.mail import Message
from app import mail

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
    mail.send(msg)
 Note that Flask-Mail support goes beyond what we are using. Bcc lists and attachments are available, for example, but we won't use them in this application. 

Follower notifications Now, we have the basic framework to send an email in place, we can write the function that sends out the follower notification:

from flask import render_template
from 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))
 We write the txt template and html template as follow: 

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
 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><img src="{{ follower.avatar(50) }}"></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 \_external=True argument to url\_for in the above templates. By default, the url_for function generates URLs that are relative to the domain from which the current page comes from. For example, the return value from url_for("index") will be /index, while in this case we want http://localhost:5000/index. In an email there is no domain context, so we have to force fully qualified URLs that include the domain, and the _external argument is just for that. The final step is to hook up the sending of the email with the "follow" function: 

from emails import follower_notification

@app.route('/follow/<nickname>')
@login_required
def follow(nickname):
    user = User.query.filter_by(nickname=nickname).first()
    # ...
    follower_notification(user, g.user)
    return redirect(url_for('user', nickname=nickname))

A problem But if you played with the application for some time and paid attention you may have noticed that now that we have email notifications when you click the follow link it takes 2 to 3 seconds for the browser to refresh the page, whereas before it was almost instantaneous. The problem is that Flask-Mail sends emails synchronously. The web server blocks while the email is being sent and only returns its response back to the browser once the email has been delivered. Can you imagine what would happen if we try to send an email to a server that is slow, or even worse, temporarily offline? Not good. This is a terrible limitation, sending an email should be a background task that does not interfere with the web server, so let’s see how we can fix this.

Asynchronous calls in Python Starting a thread each time we need to send an email is much less resource intensive than starting a brand new process, so let’s move the mail.send(msg) call into thread

from threading import Thread
from app import app

def send_async_email(app, msg):
    with app.app_context():
        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 = Thread(target=send_async_email, args=[app, msg])
    thr.start()
 The send_async_email function now runs in a background thread. Because it is a separate thread, the application context required by Flask-Mail will not be automatically set for us, so the app instance is passed to the thread, and the application context is set up manually, like we did above when we sent an email from the Python console. So now we have asynchronous emails implemented, but what if in the future we need to implement other asynchronous functions? The procedure would be identical, but we would need to duplicate the threading code for each particular case, which is not good. We can improve our solution by implementing a decorator. With a decorator the above code would change to this: 

from .decorators import async

@async
def send_async_email(app, msg):
    with app.app_context():
        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(app, msg)
 The 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
分享到:更多 ()