This video is available to students only

Sending emails

Now that we support user registration, it would be nice to send emails to confirm their registration. In the future, sending email will be important to send receipts to purchasers and other notifications.

Flask does not have a opinionated way to send emails, but we can re-use some of the same primitives that Flask comes with like Jinja and render_template. To actually send out email, we will need to communicate with an external email server that will actually send out an email. The Flask-Mail library will help us manage communicating to our email server over a protocol known as SMTP (Simple Mail Transfer Protocol).

Why use an external mail server?

A big reason to use an external provider is to reduce the chance that your email will be sent to a spam filter. Services like GMail can trust a large external email vendor IP addresses but are less likely to trust mail coming from a random IP address that has never sent email to them before. In addition, some cloud providers and internet providers disallow sending out emails over SMTP.

Configuration

Install flask_mail

Add flask_mail to your requirements.txt file and then (in your virtual environment) run, pip install -r requirements.txt

To initialize the flask_mail extension, we'll follow the same steps we have before.

First, we will initialize the extension in yumroad/extensions.py.

from flask_mail import Mail
...
mail = Mail()

Then we'll import that object in yumroad/__init__.py and call the init_app method to pass in our application configuration.

from yumroad.extensions import (csrf, db, migrate, mail, login_manager)
...
def create_app(environment_name='dev'):
    ...
    mail.init_app(app)
Sign up with a email sender

We will need to work with a third party mail provider to send out emails from our application. I'd reccomend using Mailgun. Their service includes a test domain from which you can send emails to a set of pre-registered addresses.

Go to Mailgun.org and sign up for a free account, which will allow you to send a few hundred emails per month for free. The cost from there is pay as you go if you plan on sending a lot more than a few hundred a month.

You can also use other providers like Postmark, Sendgrid, Mailjet, or AWS Simple Email Service. Regardless of which provider you get, you should be able to get STMP API credentials.

Once you have the credentials, we will need to tell our Flask application how to connect to the service. Flask-Mail looks at the following Flask configuration variables (MAIL_SERVER, MAIL_PORT).

For now, we'll add these under the BaseConfiguration, which will make these configuration settings accessible in each our environments (production, test, and development). Using os.getenv will allow us to read from the environment variables, and use a default if it's not set. It's not a good idea to let our actual credentials live in this file because it might be tracked in any version control you are using, or could easily be leaked if someone gets access to your source code.

import os

class BaseConfig:
    ...
    MAIL_SERVER = os.getenv('MAIL_SERVER', 'smtp.mailgun.org')
    MAIL_PORT = os.getenv('MAIL_SERVER_PORT', 2525)
    MAIL_USERNAME = os.getenv('MAIL_USERNAME')
    MAIL_USE_TLS = os.getenv('MAIL_USE_TLS', True)
    MAIL_PASSWORD = os.getenv('MAIL_PASSWORD')

The configuration above, works for Mailgun, but change the default port, server address, or TLS configuration to match the connection instructions provided from the service you are using. Flask-Mail provides some addition configuration settings in the documentation.

Now that Flask will lookup the credentials to send emails from our environment variables, we will need set the environment variables to the values we got from our email provider. One way we can do that is to directly prefix the flask run command with those, similar to how we used to set FLASK_ENV.

$ MAIL_USERNAME='[email protected]' MAIL_PASSWORD='abc123' flask run

While this would work, it could be unwieldy to type. For now, we'll take the path of flask run.

# One time setup per session
export FLASK_ENV='development'
export FLASK_APP='yumroad:create_app'
export MAIL_USERNAME='[email protected]'
export MAIL_PASSWORD='abc123'

flask run

Another approach is to create an executable bash script called dev.sh that runs the following script.

#!/bin/sh
FLASK_ENV='development' FLASK_APP='yumroad:create_app' MAIL_USERNAME='[email protected]' MAIL_PASSWORD='abc123' flask run

Then once your virtual environment is activated, you only need to run ./dev.sh to launch a development server. If you're using a version tracking system, be sure not to commit this file into your version tracking system

If you have a lot of environment variables, look into using a library called python-dotenv to use files to store environment

Sending basic email

To send an email, we'll need to tell Flask-Mail what we want our our email to look like first.

The Message class from Flask-Mail allows us to specify all of the fields we'll need to set for an email, like the subject, recipients, and body.

To create a simple email, with one recipient, and from [email protected], we'd pass in our subject, sender, recipients, and message body.

from flask_mail import Message

msg = Message("Our Subject",
              sender="[email protected]",
              recipients=["[email protected]"],
              body="Our content")

To actually send out the email, we'd pass the message instance into the send method from the Mail instance we just configured with our Flask application.

from yumroad.extensions import mail

mail.send(msg)

To make our messages stand out, we'll want to have the sender's actual name appear in our inbox instead of [email protected]. To do that we can use the formatting of "Sender Name <[email protected]>", or more simply, pass in a tuple to the recipients argument of message.

msg = Message("Our Subject",
              sender=("Sender Name", "[email protected]"),
              recipients=["[email protected]"],
              body="Our content")

Since we want to send these out when a user signs up, let's create a function that we can call when a user registers.

In order to keep the logic within blueprints simple and make emails testable by themselves, we'll create a separate email.py module within the yumroad folder. Our first email will be an email to thank a user for registering.

from flask_mail import Message
from yumroad.extensions import mail

DEFAULT_FROM = ('Yumroad', '[email protected]')

def send_basic_welcome_message(recipient_email):
    message = Message('Welcome to yumroad',
                      sender=DEFAULT_FROM,
                      recipients=[recipient_email],
                      body="Thanks for joining. Let us know if you have any questions!")
    mail.send(message)
yumroad-app/yumroad/email.py
from flask_mail import Message
from yumroad.extensions import mail

DEFAULT_FROM = ('Yumroad', '[email protected]')

def send_basic_welcome_message(recipient_email):
    message = Message('Welcome to yumroad',
                      sender=DEFAULT_FROM,
                      recipients=[recipient_email],
                      body="Thanks for joining. Let us know if you have any questions!")
    mail.send(message)

Now in our controller, we should call this function after we've created a User and Store

from yumroad.email import send_basic_welcome_message
...
@user_bp.route('/register', methods=["GET", "POST"])
def register():
    if current_user.is_authenticated:
        flash("You are already logged in", 'warning')
        return redirect(url_for("product.index"))

Start a new discussion. All notification go to the author.