Send Email From Flask With Flask-Mail and Jinja Templates

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

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

This lesson preview is part of the Fullstack Flask: Build a Complete SaaS App with Flask course and can be unlocked immediately with a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to Fullstack Flask: Build a Complete SaaS App with Flask with a single-time purchase.

Thumbnail for the \newline course Fullstack Flask: Build a Complete SaaS App with Flask
  • [00:00 - 00:07] In our SAS application, it's important to be able to communicate with users. And one way we can do that is to send emails.

    [00:08 - 00:16] For example, we might want to send an email when someone purchases a product. And Flask out of the box doesn't come with a way for us to send emails.

    [00:17 - 00:32] So we're going to have to use tools we already have like Ginger and Render Template to be able to render out emails. To actually send out emails, we're going to use a library called FlaskMail to manage communicating to our email server over a protocol known as SMTP.

    [00:33 - 00:41] We're going to use an external mail server because sending emails from our own server or our own computer probably won't work. Other servers will think of it as spam.

    [00:42 - 00:56] And so it's often best to use an external mail server provider to get better deliverability into our customers in boxes. So to get started, we're going to go ahead and go into VS Code and we're going to install FlaskMail here.

    [00:57 - 01:12] So we're going to go and add that to our requirements.txt file and then install it. Okay.

    [01:13 - 01:33] So something happened here which was that it installed successfully but it was trying to build wheels. The way you can resolve this error if you ever run into it is you can run pip install and now when you run requirements.txt install, it'll be able to build everything.

    [01:34 - 01:53] Going back in our code, we're going to install this extension and the way we're going to do that is by creating it in extensions.py and we're going to call this mail and then we're going to create a here mail. By now this should be standard practice.

    [01:54 - 02:02] We're going to run mail here and then mail.in it. Perfect.

    [02:03 - 02:12] Now we're also going to have to configure some things for ourselves to be able to actually send emails. First off is we're going to have to communicate with an email service provider.

    [02:13 - 02:21] So what we're going to do is sign up with Mailgun. If you go to Mailgun, you're actually going to be able to send emails by just signing up and getting your credentials.

    [02:22 - 02:33] You can use other providers like Postmar or Crescend Grid, MailJet or even AWS simple email service. Regardless of what provider you use, you'll be able to get SMTP credentials.

    [02:34 - 02:51] Your providers usually have a specific Quick Start Guide but we're just going to go ahead and set it up ourselves in our config.py. So going over to config.py, there's a few things we want to be able to set and we're just going to set them in the base config.

    [02:52 - 03:01] These are going to be the connection instructions for our application. So here we're going to say we need a mail server, we need mail server port, mail username, mail use TLS and mail password.

    [03:02 - 03:13] And so these are all things Flass Mail is going to look at and be able to connect to our server. And in our terminal, we can go back and actually set these variables.

    [03:14 - 03:32] So what we could do is say something like mail username is equal to x of whatever.com and mail password, et cetera. And then we're run flask run as well.

    [03:33 - 03:47] Now you could also just do export mail username as whatever your login is and then decide to run last run as well. So I'm going to go ahead and set my mail username and password.

    [03:48 - 04:05] You should do that too in your own console. Now that we've configured our settings, all we have to do to send an email is create a message by importing the message class here and we'll set a subject, a sender, recipients and a body.

    [04:06 - 04:13] And then to actually send the message, we'll call our extension and do dot send of that message. Okay.

    [04:14 - 04:23] So once we've seen this pattern, what we can do is we can create a file in our code editor called email.py where we'll put all of our logic for sending emails. Okay.

    [04:24 - 04:28] Let's go and create a new file within YumRoad called email.py. Okay.

    [04:29 - 04:36] Within email.py, I'm going to import something similar to what we just did. I'm going to set a default from address.

    [04:37 - 04:45] So this will show as the name as YumRoad when we're sending it and then we're going to set that example email as the default email. Okay.

    [04:46 - 04:56] So here's one that will send a basic welcome message to a user when they register. In our controller, we can go and call this function when a user registers.

    [04:57 - 05:06] So let's go ahead and go into users and import that function and call it here. So let's go and import that email there.

    [05:07 - 05:27] So we're going to say from yumroad.email, import, send basic welcome message. And then here, when we log in the user, we can go ahead and send the basic welcome message here and pass in the user like that function accepts.

    [05:28 - 05:35] So we're going to go back here and we're going to say user.email there. Okay.

    [05:36 - 05:48] In our browser, we can try and check out what the register route does. So I'm going to call this some store and enter in my email here and pass.

    [05:49 - 05:50] Okay. So it gave me that register.

    [05:51 - 05:54] So let's check if that email came in. Okay.

    [05:55 - 06:00] So here I've pulled up that email that you can see that it's loaded just by. Great.

    [06:01 - 06:13] Now what we can do is work on printing up that email by using templates. So what we're going to do is we're going to go and create a template within our code for folders and we're going to create a basic welcome template.

    [06:14 - 06:23] So we're going to create a new folder here called emails. And within emails, we can create a template like something like that's basic dot HTML.

    [06:24 - 06:47] Okay. And here, what we can do is we can show some basic details about the store pages, for example, and have some basic formatting going back in emails dot py in order to actually send this, we're going to have to create a new method called send welcome message.

    [06:48 - 07:04] And instead of just saying mail dot send message, what we're going to have to do is we're going to have to render the HTML into the text. So instead of setting the body here, what we're going to do is we're going to set message dot HTML is equal to call to render template.

    [07:05 - 07:13] And we're going to say emails, welcome basic dot HTML. And then we use the store variable in here.

    [07:14 - 07:23] So we're going to have to pass that in as well. So we're going to say store, and then we're going to have to hear, instead of taking in recipient email, we're going to have taken a user.

    [07:24 - 07:36] And then we can set the user to user dot email. And then we can say the user is equal to user dot store.

    [07:37 - 07:47] Now with this nicer looking email, that's going to look a little better. And it's going to have some formatting, but it's really not a huge change aside from adding a few links to the page.

    [07:48 - 07:55] So what we want to do is add some more templating. And so our email doesn't just look like a plain text email.

    [07:56 - 08:08] So if we're writing a lot of CSS by hand, we can use an existing email template as our base that we'll work off of. So the specific thing on template I'm going to work off of is on GitHub here.

    [08:09 - 08:21] And it basically gives us an outline for a template that we can use. And so what we want to do is grab the email dot HTML file here and make a template out of this.

    [08:22 - 08:27] So that's what we're going to do here. So back in our text editor, I'm going to go and create a base template file here.

    [08:28 - 08:38] And what I'm going to do is I'm going to paste in the content that we got from the repository there. So this is a lot of content here.

    [08:39 - 08:47] I'm just going to take keep all of this the same. And then I'm going to create a block at the body here where this is main content area.

    [08:48 - 08:57] And so what we can do is create a content area right here. So this looks like the core content of the page.

    [08:58 - 09:11] So let's go and set a block there that says block body and and block. Okay.

    [09:12 - 09:21] Now within our content for this page, we can create a new file. I'm going to call this one welcome, pretty dot HTML.

    [09:22 - 09:31] We can use the base layout from email slash base and then define our content block. Here we're going to extend the base and then we're going to define our own block as well.

    [09:32 - 09:48] And so within here, we can paste in the content of what we had in welcome basic . And in order to get a button, we're going to have to use some email formatting, which means that we're not going to be able to just write CSS like we were normally able to.

    [09:49 - 09:57] We're going to have to write buttons. And the reason for this is because email clients are a little outdated and they don't quite use modern browser techniques.

    [09:58 - 10:08] So we can't just use all the same styling we use in HTML documents normally. This code here is going to enable us to actually create a button.

    [10:09 - 10:13] Okay. And bottom, I'm going to end our block.

    [10:14 - 10:24] Another thing we might want to do at the base is go ahead and check these details to be more appropriate. So we don't have an unsubscribe link.

    [10:25 - 10:27] So let's remove that. And then this might not be our address.

    [10:28 - 10:33] So we're going to leave that there. And then what we can do is we can change this block if needed or not.

    [10:34 - 10:41] So I'm just going to remove that for now. And right now we have a full HTML template here.

    [10:42 - 10:50] One other thing you'll notice here is that there is some preheader text here. So this is what our email clients kind of show us the very first line of our of our email.

    [10:51 - 11:00] So what we can do is we can define something here that allows us to substitute that in. So we can call this preview text.

    [11:01 - 11:08] And if it's not defined and passed into the render template goal, it's just going to be like, which is fine. Okay.

    [11:09 - 11:19] So let's go back in emails.py and now configure a new mailer that actually sends a welcome message the way we want. So we can leave this as welcome to YumRoad.

    [11:20 - 11:26] But I'm going to go and say we want to format this to say the store name. So let's go and get the store as user.store.

    [11:27 - 11:33] And then here, what is everything welcome basic? We're going to render welcome pretty.

    [11:34 - 11:48] We're going to pass in the store as well as some preview text. So this is something like here is how you get started with YumRoad.

    [11:49 - 11:58] Okay. So this right now looks like a way better template to work with.

    [11:59 - 12:06] So let's go and get rid of send basic welcome message and we can delete this file as well. Okay.

    [12:07 - 12:21] So let's get rid of the other email template. And now we can go back in our registration blueprint and set just do send welcome message.

    [12:22 - 12:36] We're also going to make sure we're going to pass in the user to send welcome message here. And then looking back at our templates, we're going to go and make sure that everywhere we're referencing products, we're making sure we're referencing the right blueprint there.

    [12:37 - 12:51] So once we've confirmed that the URL for is accurate and we're correctly passing in send welcome message, a user, we can then go ahead and try it out in our test app link. So now that we're on our application, we can go ahead and try it out.

    [12:52 - 13:04] So we can say our store name is going to be a sample bookstore and my email is going to be something unique there. And then we're going to say test and then we're going to see that we were able to log in.

    [13:05 - 13:08] Now let's see what the email looks like. Okay.

    [13:09 - 13:12] Here's what that email looks like. So that looks a lot better.

    [13:13 - 13:22] And then this add products link links to our product page. Now one thing you'll notice here is this link that we're getting is actually not linking to the full URL.

    [13:23 - 13:33] We need to make sure that the URL forts that we include in our email templates include that external parameter. So let's go ahead and go into our code editor and make that happen.

    [13:34 - 13:50] So here every time we do URL for, we're going to need to pass in external is true. That way we get the full path to our application.

    [13:51 - 13:59] In order to check if our mail's were sent, we're going to have to create a way to monitor whether our mail was sent. The way we can do that is through flash mail.

    [14:00 - 14:12] Flash mail actually provides us with the way that we can monitor emails that are sent. So what we can do there is we can import our mail there and then we can create a fixture.

    [14:13 - 14:26] And I'm going to call it an outbox so that we can see what's going out in the outbox. And then in our emails test, which we'll need to create here.

    [14:27 - 14:43] So once we create email test, we're going to go ahead and create a few things here. The very first thing we're going to test is making sure that an email was able to be sent.

    [14:44 - 14:51] So to do that, we're going to have to email import the email. So from ramar.email import send welcome message.

    [14:52 - 15:02] Then to test if the email was created, we're going to say test email sending. We're going to need app, a database, the mail outbox.

    [15:03 - 15:13] And we're also going to make sure that this is an authenticated request. This last one's not super important, but what it does do is it helps create a user for us.

    [15:14 - 15:24] So the fact that we're logging is just not as relevant, but it does ensure that we have a user here. So what we can say is our user is going to be the very first user in our database.

    [15:25 - 15:35] Then what we're going to do is we're going to send welcome message to user. Now what we can do is we can use the mail outbox to assert that one was sent.

    [15:36 - 15:49] Then we can also assert the mailbox that it is creating a test store. Now you'll notice that our fixture is probably not creating a test store as well.

    [15:50 - 15:55] So we should do that. Since that's an assumption we depend on.

    [15:56 - 16:15] So the way we're going to do that is by importing store as well from our models and then creating a store while we're at it. So here we're going to say a store and then we're going to say that's associated with the new user and then we're going to command.

    [16:16 - 16:21] Great. So now we can assert that what it should tell us is welcome to YumRoad Test Store.

    [16:22 - 16:37] And if we look at our email that should correspond similar to what our email should say. Now going back to our terminal, we can go and run the test with test code coverage.

    [16:38 - 16:49] You can see that the email test passed and we have 100% code coverage. In the next set of videos we're going to be talking about payments and how we can hook this up to be an actual store.