Python Flask Push Notifications (Step By Step Example)

Welcome to a tutorial on how to send push notifications in Python Flask. It is no mystery that web browsers are capable of displaying notifications. But just how do we send push notifications in Python Flask? Let us walk through an example, step by step. Read on!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

QUICK NOTES

  • Create a project folder, e.g. D:\push, unzip the code inside this folder.
  • Navigate to the project folder in the command line cd D:\push, create a virtual environment to not mess up your other projects.
    • virtualenv venv
    • Windows – venv\scripts\activate
    • Mac/Linux – venv/bin/activate
  • Get all the packages – pip install flask ecdsa pywebpush
  • Run python S2_vapid.py to generate a pair of public/private keys.
    • Change the public key in templates/S3_perm_sw.html to your own.
    • Change the private key and email to your own in S5_server.py.
  • Launch python S5_server.py and access http://localhost.
If you spot a bug, feel free to comment below. I try to answer short questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

SCREENSHOT

 

EXAMPLE CODE DOWNLOAD

Click here to download the source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

 

PYTHON FLASK PUSH NOTIFICATIONS

All right, let us not get into the example of sending a push notification in Python Flask.

 

STEP 1) SERVER SETUP

1A) VIRTUAL ENVIRONMENT

Before we get into the code, let us start by setting up the “usual” virtual environment.

  • virtualenv venv
  • Windows – venv\scripts\activate
  • Mac/Linux – venv/bin/activate

 

1B) INSTALL REQUIRED MODULES

Next, run pip install flask ecdsa pywebpush to download all the required modules.

 

STEP 2) GENERATE VAPID KEYS

S2_vapid.py
# (A) REQUIRED MODULES
import base64
import ecdsa 

# (B) GENERATE KEYS
# CREDITS : https://gist.github.com/cjies/cc014d55976db80f610cd94ccb2ab21e
pri = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p)
pub = pri.get_verifying_key()
keys = {
  "private" : base64.urlsafe_b64encode(pri.to_string()).decode("utf-8").strip("="),
  "public" : base64.urlsafe_b64encode(b"\x04" + pub.to_string()).decode("utf-8").strip("=")
}
print(keys)

Now that the environment is ready, we will generate a pair of public/private VAPID keys. This script only needs to run once, although you can always run it again to generate a new pair of keys.

P.S. VAPID stands for “Voluntary Application Server Identification”. In simple terms, to prevent your push notifications from being hijacked.

P.P.S. Keep the private key safe. It should only be used on the server-side, and never be exposed publically.

 

 

STEP 3) CLIENT PAGE

3A) GET PERMISSION TO DISPLAY NOTIFICATIONS

templates/S3_perm_sw.html
// (A) OBTAIN USER PERMISSION TO SHOW NOTIFICATION
window.onload = () => {
  // (A1) ASK FOR PERMISSION
  if (Notification.permission === "default") {
    Notification.requestPermission().then(perm => {
      if (Notification.permission === "granted") {
        regWorker().catch(err => console.error(err));
      } else {
        alert("Please allow notifications.");
      }
    });
  } 
 
  // (A2) GRANTED
  else if (Notification.permission === "granted") {
    regWorker().catch(err => console.error(err));
  }
 
  // (A3) DENIED
  else { alert("Please allow notifications."); }
}

Now that we have the pair of keys, let us put them aside for a minute. The very first thing we need in the HTML page is to get the user’s permission to show notifications. Yes, we cannot proceed any further if the push notifications cannot show.

 

3B) REGISTER SERVICE WORKER

templates/S3_perm_sw.html
// (B) REGISTER SERVICE WORKER
async function regWorker () {
  // (B1) YOUR PUBLIC KEY - CHANGE TO YOUR OWN!
  const publicKey = "YOUR-PUBLIC-KEY";
 
  // (B2) REGISTER SERVICE WORKER
  navigator.serviceWorker.register("S4_sw.js", { scope: "/" });
 
  // (B3) SUBSCRIBE TO PUSH SERVER
  navigator.serviceWorker.ready
  .then(reg => {
    reg.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: publicKey
    }).then(
      // (B3-1) OK - TEST PUSH NOTIFICATION
      sub => {
        var data = new FormData();
        data.append("sub", JSON.stringify(sub));
        fetch("/push", { method:"POST", body:data })
        .then(res => res.text())
        .then(txt => console.log(txt))
        .catch(err => console.error(err));
      },
 
      // (B3-2) ERROR!
      err => console.error(err)
    );
  });
}

This function regWorker() will only run after the user has granted permission to show notifications.

  • (B1) A gentle reminder to change the public key to your own.
  • (B2) Proceed to register S4_sw.js as a service worker.
  • (B3) When the service worker is ready.
    • (B3) reg.pushManager.subscribe() We subscribe the service worker to the push service.
    • (B3-1) On successful registration, we immediately send the subscribed service worker sub to the server and request a push notification demo.

 

 

STEP 4) SERVICE WORKER

static/S4_sw.js
// (A) INSTANT WORKER ACTIVATION
self.addEventListener("install", evt => self.skipWaiting());
 
// (B) LISTEN TO PUSH
self.addEventListener("push", evt => {
  const data = evt.data.json();
  self.registration.showNotification(data.title, {
    body: data.body,
    icon: data.icon,
    image: data.image
  });
});

For those who are new, a service worker is a piece of Javascript that will run in the background. Even when the user is not on the website itself. So I don’t think this needs a lot of explanation – It simply shows the notification when a push message is received from the server.

 

STEP 5) FLASK SERVER

5A) INIT

S5_server.py
# (A) INIT
# (A1) LOAD MODULES
from flask import Flask, render_template, request, make_response, send_from_directory
from pywebpush import webpush, WebPushException
import json
 
# (A2) FLASK SETTINGS + INIT - CHANGE TO YOUR OWN!
HOST_NAME = "localhost"
HOST_PORT = 80
VAPID_SUBJECT = "mailto:your@email.com"
VAPID_PRIVATE = "YOUR-PRIVATE-KEY"
app = Flask(__name__)
# app.debug = True

The first section of the Flask server script needs no explanation… Just loading the required modules, and a whole bunch of settings. Remember to change the private key and email to your own.

 

 

5B) VIEWS

S5_server.py
# (B) VIEWS
# (B1) "LANDING PAGE"
@app.route("/")
def index():
  return render_template("S3_perm_sw.html")
 
# (B2) SERVICE WORKER
@app.route("/S4_sw.js")
def sw():
  response = make_response(send_from_directory(app.static_folder, "S4_sw.js"))
  return response
 
# (B3) PUSH DEMO
@app.route("/push", methods=["POST"])
def push():
  # (B3-1) GET SUBSCRIBER
  sub = json.loads(request.form["sub"])
 
  # (B3-2) TEST PUSH NOTIFICATION
  result = "OK"
  try:
    webpush(
      subscription_info = sub,
      data = json.dumps({
        "title" : "Welcome!",
        "body" : "Yes, it works!",
        "icon" : "static/i-loud.png",
        "image" : "static/i-zap.png"
      }),
      vapid_private_key = VAPID_PRIVATE,
      vapid_claims = { "sub": VAPID_SUBJECT }
    )
  except WebPushException as ex:
    print(ex)
    result = "FAILED"
  return result

Next, we have the “service endpoints”.

  • (B1) / will serve templates/S3_perm_sw.html above.
  • (B2) /S4_sw.js will “map” to static/S4_sw.js, the reason for doing this is to set the scope to /. That is, if we register static/S4_sw.js, the service worker will only apply to https://your-site.com/static/*. Not what we want.
  • (B3) /push Post a subscribed worker here to send a push notification.

 

5C) LAUNCH

S5_server.py
# (C) START
if __name__ == "__main__":
  app.run(HOST_NAME, HOST_PORT)

Start the Flask server. Captain Obvious at your service, and that’s it.

 

EXTRA BITS & LINKS

That’s all for the search example, and here are a few extra bits that may be useful to you.

 

NOT A “COMPLETE” DEMO

Yes, this is only a simple example. This is not how a production server works.

  • In S3_per_sw.html (B3), we should send the subscribed worker to the server first, and save it into a database.
  • At a later date, call S5_server.py (B3). This will retrieve all subscribers from the database, and mass sends the push notification to everyone.

 

 

I DENIED NOTIFICATIONS

Once denied, the browser will not prompt for permission again. The only way to “fix” this, is to manually click on the icon beside the URL bar. Then, enable the notifications.

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

Leave a Comment

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