Send an Email from your Flask Server


Description

There are times when you want your server to send an email. You can send an email to a user to notify them that their password has been reset, or send an email to yourself (or an Admin) to notify of a failed login event, etc.

In this case, we are building on our self-monitoring project and will send an email each time our server starts (assuming network connectivity, of course). This will notify us that our server was restarted so we can review the logs.

My example uses Gmail, but most SMTP-supported email accounts should work. Gmail does require that you setup an App Password for this purpose in order to use it in this way. I’ll cover that below.

This will give you the foundation you need to implement sending emails from your server however and whenever you need.

Parameters

This is some information about this project and the conditions under which it was done. If you try to replicate it in the future and it doesn’t work, you can evaluate these parameters to see if any changes between these and your configuration might have impacted your results. An example might be changes to future versions of Python or Flask.

  • Date: November 9, 2021
  • Skill: Beginner
  • Raspberry Pi Model(s): Zero-W, 3B+, 4B
  • OS: Raspberry Pi OS version 10 (Buster)
  • Python Version: 3.7.3
  • Flask Version: 1.0.2

Gmail Setup Steps

In order to use your Gmail account with Flask-Mail, you will need to go through a few steps to generate an App Password. Follow the steps below.

If you don’t have a Gmail account, you can do a search to determine what configuration settings you need to use. Or you can create one (it’s free) and follow these steps.

  1. First, open Gmail or any other Google account that you are logged into. Click on your account (circle with your initial) and choose Manage your Google Account.
  2. Select the Security tab.
  3. Select App passwords. You may have to enter your password again here.
  4. Click on Select app and choose Mail.
  5. Click on Select device and choose Other. You will be prompted for a name. Enter something like RasPi Gmail.
  6. Click GENERATE.
  7. You will be given a 16-character password that you can now use to configure Flask-Mail below. You can enter the password either with or without the spaces when you use it. Copy it and keep it somewhere safe. Don’t share it with anyone! It grants full access to your email account!
  8. If needed, you can return to this Google Account setting and revoke the App Password you generated.

Adding Flask-Mail to our server

  1. First you will need to install Flask-Mail. Open a terminal and enter the following command.
sudo pip3 install flask-mail
  1. Let’s start with an updated version of the self-monitoring project and add Flask-Mail support. I’ll provide the entire contents of the sample.py server code here and highlight the code related to Flask-Mail:
from flask import Flask, request, render_template, send_from_directory
from datetime import datetime
import io
import os
import subprocess
import json

from flask_mail import Mail, Message

EMAIL_ADDRESS = 'youremail@gmail.com'
EMAIL_PASSWORD = 'your_password'

SERVER_NAME = 'Sample Server'
START_DATE = datetime.now().strftime('%m/%d/%y %I:%M:%S %p')
PI_MODEL = 'Unknown'

proc = subprocess.Popen(["cat", "/sys/firmware/devicetree/base/model"], stdout=subprocess.PIPE)
(out, err) = proc.communicate()
if not err:
     PI_MODEL = out.decode('utf-8')
else:
    print("Error get Pi Model: %s" % err)

def send_email(subject, recipients, text=None, html=None, cc=None, bcc=None):
    print('Sending email...')
    status = False
    try:
        msg = Message(subject, sender=EMAIL_ADDRESS, recipients=recipients, cc=cc, bcc=bcc)
        msg.html = html
        msg.body = text
        mail.send(msg)
        status = True
    except Exception as e:
        print(f'Failed sending email: {str(e)}')
    return status

def get_health_data():
    curDateTime = datetime.now().strftime('%m/%d/%y %I:%M:%S %p')

    resp = {'serverName': SERVER_NAME, 'piModel': PI_MODEL, 'startDate': START_DATE, 'curDate': curDateTime}

    #Get ip.txt contents
    try:
        f = open('/home/pi/ip.txt', 'r')
        fdata = f.readlines()
        fcnt = 'Fail Cnt: %s' % (fdata[0].strip())
        rcnt = 'Reboot Cnt: %s' % (fdata[1].strip())
        f.close()
    except:
        fcnt = 'Fail Cnt: No Data'
        rcnt = 'Reboot Cnt: No Data'
    resp['fcnt'] = fcnt
    resp['rcnt'] = rcnt

    #Get reboot.txt contents
    try:
        f = open('/home/pi/reboot.txt', 'r')
        fdata = f.readlines()
        rhist = []
        for x in range(len(fdata)):
            #rhist += fdata[x]
            rhist.insert(0, fdata[x])
    except:
        rhist = []
    resp['rhist'] = rhist

    # Report available disk space
    stat = os.statvfs('/home/pi')
    gbFree = '%0.2f GB' % (stat.f_bfree*stat.f_bsize/1024/1024/1024)

    # Report CPU Temp
    try:
        tFile = open('/sys/class/thermal/thermal_zone0/temp')
        temp = float(tFile.read())
        print('Temp: %f' % temp)
        tempC = '%0.1f C' % (temp/1000)
        tempF = '%0.1f F' % ((temp/1000) * 1.8 + 32)
    except:
        print('Error loading temp...')
        tempC = 'ERR'
        tempF = 'ERR'

    tFile.close()

    resp['serverName'] = SERVER_NAME
    resp['piModel'] = PI_MODEL
    resp['curDate'] = curDateTime
    resp['startDate'] = START_DATE
    resp['gbFree'] = gbFree
    resp['tempC'] = tempC
    resp['tempF'] = tempF
    return resp

app = Flask(__name__)

app.config['MAIL_SERVER']='smtp.gmail.com'
app.config['MAIL_DEBUG'] = False
app.config['MAIL_PORT'] = 465
app.config['MAIL_USERNAME'] = EMAIL_ADDRESS
app.config['MAIL_PASSWORD'] = EMAIL_PASSWORD
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
mail = Mail(app)

with app.app_context():
    send_email(subject=f'{SERVER_NAME} Started', recipients=[EMAIL_ADDRESS], text=f'Server Started at: {START_DATE}', html=f'<h4>Server Started at: {START_DATE}</h4>')

@app.route('/')
def index():
    data = {'serverName': SERVER_NAME}
    data['piModel'] = PI_MODEL
    return render_template('index.html', healthData=data)

@app.route('/healthcheck')
def healthcheck():
    return render_template('health.html', healthData=get_health_data())

@app.route('/health-data')
def healthData():
    return get_health_data()

@app.route('/reboot', methods=['GET', 'POST'])
def reboot():
    if request.method == 'GET':
        return render_template('reboot.html', serverName=SERVER_NAME)
    else:
        ret = os.system('sudo reboot')
        return 'Rebooting'

@app.route('/shutdown', methods=['GET', 'POST'])
def shutdown():
    if request.method == 'GET':
        return render_template('shutdown.html', serverName=SERVER_NAME)
    else:
        ret = os.system('sudo shutdown -h now')
        return 'Shutting down'

@app.route('/get-background')
def getBackground():
        return send_from_directory('/home/pi/sample/static/', 'favicon.png')

@app.route('/apple-touch-icon-152x152.png')
@app.route('/apple-touch-icon-152x152-precomposed.png')
@app.route('/apple-touch-icon-120x120-precomposed.png')
@app.route('/apple-touch-icon-120x120.png')
@app.route('/apple-touch-icon-precomposed.png')
@app.route('/apple-touch-icon.png')
@app.route('/favicon.ico')
def favicon():
    print('favicon')
    return send_from_directory('/home/pi/sample/static/', 'favicon.png')

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)

The password you enter for EMAIL_PASSWORD will be the App Password that you generated above.

In our send_email() function, you’ll notice that we accept a few parameters, some of which are optional. There are more available (I’ll provide a link to the official documentation below), but here are the ones that I’m using:

  • subject: A text string that will be the message Subject.
  • recipients: A list of recipient email addresses.
  • text: You can optionally pass a plain text message body for recipients that don’t support html.
  • html: You can optionally pass an html message body for recipients that support html. You can pass both text and html. If html is supported, it will be used as the body, otherwise it will use the plain text.
  • cc: A list of cc email addresses.
  • bcc: A list of bcc email addresses.

Notice that we wrap the call in a try/except so we can gracefully handle a failure and can display the error message in the terminal. We also return a status so the caller can determine whether or not is was successful if needed.

Lines 95 – 102 add some needed configuration settings to our app context and then creates a mail object using the Flask app. Note that the creation of the mail object needs to be after the configurations are set.

In lines 104 and 105 we need to use the with app.app_context(): statement because the app context hasn’t been established at this point during startup. If you call send_email() from any route functions or other functions that are executed after startup, you won’t need this with statement.

  1. Since this is an updated version of the server, I’ll include the respective HTML files as well:
index.html
<!DOCTYPE html>
<html>

<head>
  <style>
    body {
      background-image: url("/get-background");
      background-repeat: no-repeat;
      background-position: center top;
      background-color: lightblue;
      color: blue;
      font-size: 28px;
    }
    a {
      font-size: 32px;
    }
    .h-space {
      color: black;
      margin-top: 100px;
      text-align: center;
    }
    .h-center {
      margin-top: -25px;
      text-align: center;
    }
    .p-space {
      margin-top: 50px;
    }
  </style>
</head>

<body>
  <h1 class="h-space">{{healthData['serverName']}}</h1>
  <h4 class="h-center">({{healthData['piModel']}})</h4>
  <p><a href='/healthcheck'>Health Check</a></p>
  <p class="p-space"><a href='/reboot'>Reboot</a></p>
  <p><a href='/shutdown'>Shutdown</a></p>
</body>
</html>
health.html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='utf-8'>
  <meta name='viewport' content='width=device-width, initial-scale=1'>
  <title>Healthcheck</title>

  <style>
    body {
      background-image: url("/get-background");
      background-repeat: no-repeat;
      background-position: center top;
      background-color: lightblue;
      color: blue;
      font-size: 28px;
    }
    a {
      font-size: 32px;
    }
    .h-space {
      color: black;
      margin-top: 100px;
      text-align: center;
    }
    table, th, td {
      border: 1px solid black;
      border-collapse: collapse;
      padding: 15px;
    }
    th {
      text-align: right;
      background-color: skyblue;
    }
    td {
      background-color: white;
    }
  </style>

</head>

<body>
  <h1 class="h-space">{{healthData.serverName}}</h1>
  <h3>Health Check</h3>
  
  <table>
    <tr>
      <th>Model:</th>
      <td><b>{{healthData.piModel}}</b></td>
    </tr>
    <tr>
      <th>Health Status as of:</th>
      <td><b>{{healthData.curDate}}</b></td>
    </tr>
    <tr>
      <th>Running Since:</th>
      <td><b>{{healthData.startDate}}</b></td>
    </tr>
    <tr>
      <th>Available Disk Space:</th>
      <td><b>{{healthData.gbFree}}</b></td>
    </tr>
    <tr>
      <th>CPU Temperature:</th>
      <td><b>{{healthData.tempF}} ({{healthData.tempC}})</b></td>
    </tr>
  </table>
  <p id="fcnt">{{healthData.fcnt}}</p>
  <p id="rcnt">{{healthData.rcnt}}</p>
  <p>Reboot History:
    <pre id="rhist">
      {%- if healthData.rhist -%}
        {%- for data in healthData.rhist -%}
          {{loop.revindex+1}}: {{data}}
        {%- endfor %}
      {%- else -%}
        None
      {%- endif %}
    </pre>
  </p>
  <p><a href='/'>Index</a></p>
  
  <script>
//          {loop.revindex}: {data}
  </script>
</body>

</html>
reboot.html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='utf-8'>
  <meta name='viewport' content='width=device-width, initial-scale=1'>
  <title>Reboot</title>

  <style>
    body {
      font-size: 24px;
      background-color: lightblue;
      color: blue;
    }
    
    input {
      font-size: 24px;
    }
  </style>
 </head>
 <body>
   <form action="/reboot" method="POST">
     <h1>Reboot {{serverName}}?</h1>
     <label for="submit">Are you sure?</label><br /><br />
     <input id="submit" type="submit" value="Reboot Now"><p></p>
     <a href="/"><input id="cancel" type="button" value="Cancel"></a>
   </form>
 </body>
 </html>
shutdown.html
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='utf-8'>
  <meta name='viewport' content='width=device-width, initial-scale=1'>
  <title>Shutdown</title>

  <style>
    body {
      font-size: 24px;
      background-color: lightblue;
      color: blue;
    }
    
    input {
      font-size: 24px;
    }
  </style>
 </head>
 <body>
   <form action="/shutdown" method="POST">
     <h1>Shutdown {{serverName}}?</h1>
     <label for="submit">Are you sure?</label><br /><br />
     <input id="submit" type="submit" value="Shutdown Now"><p></p>
     <a href="/"><input id="cancel" type="button" value="Cancel"></a>
   </form>
 </body>
 </html>

Summary

You now have the foundation you need for sending emails from your Flask server. I’m sure you’ll find many uses for this feature!

Learn More

Raspberry Pi / Raspberry Pi OS

Python / HTML / CSS

Flask

Jinja2

Flask-Mail

1 thought on “Send an Email from your Flask Server”

  1. Generally I do not learn аrticle on blogs, but I wish to say that this write-up very pressured me to taқe a ⅼoօk
    at аnd do ѕo! Your ѡriting taѕte has bеen surprised me.
    Thanks, very nice article.

Leave a Comment

Your email address will not be published. Required fields are marked *