How to set up email and message notifications for a Python application
Tags: python, notifications, tutorial, programming
Published: 2022-12-13 10:01:20

# Summary

So, you've prepared a nice Python application that is now running (or almost there), but you still need to manually check the results, or see that it is actually running smoothly... so what happens if you're away and suddenly something stops working as it should? It will continue to be broken until you decide to check on it and fix it, but that's not very practical.

It would be MUCH better to implement some kind of notification system so you're alerted when something happens. It might be something bad like your application being incapable of properly handling an unexpected error, or something good like some heavy program finishing running having the results ready for you to see.

Here we'll go over a simple way to implement a quite good and reliable notification system using Python, GMail, and Telegram. 
You might want to ask "why two channels?". Well, a notification system might fail, and since the lack of a notification is not something that will be noted, then it might be completely overlooked and then you might miss an important notification. That would be a big fail. 

To avoid that case, it's important to have some redundancy. Of course that it's also possible for both notification systems to fail, but to do so simultaneously is way less likely, so we can feel a lot more secure on our notifications if we have more than one system implemented in parallel. If you're application is of critical importance to you, it's also possible to add more channels in parallel, but here we'll limit ourselves to two.

Also, if on a given occasion we only receive the notification via one of the channels, we know that we need to check on the other one because it might be broken.

NOTE: In this post I'll only share code snippets but I'll ignore very important features that are not essential to understand the key concept, and that can also get in the way of it on a first read (e.g., classes and functions definitions, file path resolution, error handling, logging, tests, etc). By all means, you should try to implement these code snippets together with all these other ingredients in a way that makes sense for your application.

# Email notifications with GMail

To send notifications using GMail we need, naturally, a Google account.
I think that the best approach is to create a new account to send these emails, to avoid using one's personal Google account in case there is a security breach and someone gets access to the password app we'll create.
Having a new account created just for these purposes means that even if someone gets access to the password app, there's no much harm that they can do, or non at all.

So let's create a new Google account, and call it `notifier.bot@gmail.com` (this exact address is probably taken but the actual account name is not important, you can create it with whatever name you prefer).

After going over all the steps, we need to enable [2 step verification][google-2-step-verification] first, because it is a pre-requisite that Google has before we are able to create a Password App.
Simply go over the steps and use one of the methods provided for 2 step verification.

Once that is set up, we can now [create a App Password][google-app-password].
The steps are quite simple, just a few clicks and we get a new App Password (let's call it `<app-password>`) that we can use to authenticate to our account using it instead of our regular passowrd.
The obvious benefit is that we can use this App Password in our app and we don't need to include our actual account password, so if by mistake it is filtered and someone gains access to it, we can simply revoke it and we never lose the control of the account.

Now that we have a notifier Google account and we created a Password App for it, the code to create and send an email is quite simple:

```python
import json
import os
import smtplib
from email.message import EmailMessage

from dotenv import load_dotenv
_ = load_dotenv()

# Read email address and app password from environment variables.
gmail_account = os.environ.get("GMAIL_ACCOUNT")
gmail_password = os.environ.get("GMAIL_PASSWORD")

# Create message.
message = EmailMessage()
message["To"] = "danielcaballero88@gmail.com"
message["From"] = "danielcaballero88@gmail.com"
message["Subject"] = subject
message.set_content(content)

# Send email.
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp:
    smtp.login(gmail_account, gmail_password)
    smtp.send_message(message)
```

Note that the secrets (GMail account and password) are read from environment variables that are previously loaded from a `.env` file. It's VERY important that these secrets be kept out of the code for security reasons. 
If you're not familiar with this, the [official documentation of the `python-dotenv` package][python-dotenv] is quite easy to follow.

[google-2-step-verification]: https://support.google.com/accounts/answer/185839?hl=en&co=GENIE.Platform%3DDesktop

[google-app-password]: https://support.google.com/accounts/answer/185833?hl=en

[python-dotenv]: https://pypi.org/project/python-dotenv/

# Message notifications using Telegram

To send notifications via Telegram we'll create a new Telegram bot, add it to the Telegram chat where we want to receive the notifications, and implement the code to send the notification message to that channel.

The best approach is to follow the [official tutorial for creating and setting up a bot][telegram-bot-guide].
Nonetheless, here I'll put a summary of the steps I followed.

[telegram-bot-guide]: https://core.telegram.org/bots/tutorial

## Create a Telegram bot

Simply look for the `@BotFather` in the Telegram messaging app, and ask to create a new bot by sending a `/newbot`
message to it.
It will prompt for the bot name, we can give something like `NotifierBot`, but this one will most likely be taken, so
use a unique one (and by all means try to use CamelCase, just for consistency sake).
The Bot Father will then provide a token for the bot (let's refer to it as `<token>`), which is meant to be kept secret,
just save it for now.

Now you can look for your bot and start interacting with it, but since there is nothing implemented for it, it will be
like talking to a wall (in the search bar look for `@NotifierBot`, or whatever name you used preceded by a `@` sign, and you should see it there).
What we can do, however, is to get the chat id for this chat, or add the bot to another chat if we already have a chat dedicated to notifications, or we can create a new chat for it, whatever you choose is up to you, and it won't affect the next steps.

We do need to get the chat id however, because we want the bot to send messages to that particular chat, so we need to
visit the following url:

```
https://api.telegram.org/<token>/getUpdates
```

Just replace `<token>` with your actual bot token and visit it in your browser.
You'll get a JSON response displayed in the browser window and somewhere in it, something like this:

```
...
"my_chat_member":{"chat":{"id":<chat_id>,"
...
```
where `<chat_id>` is a positive or negative number.

Copy that number and save it too.

## Implement the notifications

Now with both the `token` and the `chat_id` we can construct the call to send the message that we want, using the
[sendMessage][telegram-sendmessage-doc] method of the Telegram API:

[telegram-sendmessage-doc]: https://core.telegram.org/method/messages.sendMessage


```python
import requests
from dotenv import load_dotenv
_ = load_dotenv()

# Read token and chat_id from environment variables.
telegram_token = os.environ.get("TELEGRAM_TOKEN")
telegram_chat_id = os.environ.get("TELEGRAM_CHAT_ID")

headers = {
    "Content-Type": "application/json",
    "Proxy-Authorization": "Basic base64",
}
data_dict = {
    "chat_id": telegram_chat_id,
    "text": <message>,  # Replace <message> with the intended message.
    "parse_mode": "HTML",
    "disable_notification": True,
}
data = json.dumps(data_dict)
url = f"https://api.telegram.org/bot{telegram_token}/sendMessage"

response = requests.post(
    url, data=data, headers=headers, verify=False, timeout=10
)
```

Again note that we are reading the secrets from environment variables that are previously loaded from a `.env` file.

# Conclusion

Here I showed how we can set up notifications using GMail and Telegram, which can be incredibly useful for many practical applications when we need to be notified or alerted immediately when something (either good or bad) happens.

Everything is explained and links to the official documentation for each case is also provided. 

You should be able to adapt these code snippets to your use case, and also feel free to ask questions and I'll do my best to answer them.