Build an Email Tracker App with Flask & Telegram | Get Instant Email Read Notifications on Telegram

raunit - in Others
Create an email tracker app using Flask to send read notifications to Telegram. Learn Base64 decoding, IP-based location tracking, and how to deploy with Docker effortlessly.

In this tutorial, we'll walk through building an email tracker app using Flask, where you can track when an email is opened and get instant Telegram notifications about it.

Link to the Github repository is provided below.

1. Setting Up Your Flask Application

We begin by creating a Flask application. Flask is a lightweight Python web framework that helps us create the API for our email tracking system. Install Flask using:

pip install flask PyYAML requests
or
pip install -r requirements.txt

2. Decoding the Base64-encoded Email

Advantages of Base64 Encoding:
  • Obfuscation: It hides the email address from direct view in the URL, making it harder for casual observers or crawlers to detect the email.
  • Prevents Direct Harvesting: Since the email is encoded, it is harder for automated scrapers to collect email addresses directly from your URLs.
Disadvantages:
  • Easily Reversible: Base64 encoding is not a secure method. Anyone with access to the URL or a slight knowledge of Base64 encoding can decode it back to the original email address without any effort.
  • No Privacy Protection: Since Base64 is not encryption, it offers no real privacy for the email address itself. If the tracker is intercepted or monitored, the email address can easily be decoded.
Better Approaches for Protecting Email:

If you want to protect the email address and prevent it from being exposed, consider using one of the following methods:

1. URL Hashing

Instead of encoding the email address, you can hash it using a secure hashing algorithm like SHA-256. This way, the email is not stored or exposed in any way, but you can still track it uniquely.

https://yourdomain.com/track/hashed-email

This method ensures that the email is not directly visible in the URL but still allows for unique tracking.

2. Use Tokens

You can generate a token for each email and store the actual email in a secure database. The URL can then include the token, and upon receiving the request, you map the token back to the email.

https://yourdomain.com/track/<token>

The token is securely stored in your database, and only the server knows which email corresponds to which token.

3. JWT (JSON Web Token)

For added security, you could use JWT to encode the email along with some metadata, but it’s important to ensure the JWT is signed to prevent tampering.

Conclusion:

Base64 encoding is better than nothing but should not be relied on for privacy or security. It’s mostly for obfuscation, not protection. If you are serious about protecting the email address and improving privacy, it's better to use more secure techniques like tokenization or hashing.

The email ID in the tracking URL is Base64 encoded for privacy. We will decode it and verify if it's a valid email address using regular expressions.

email = base64.b64decode(email_id).decode('utf-8')
if not is_valid_email(email):
    return jsonify({"error": "Invalid email address"}), 400
        

3. Tracking the IP Address and Location

Once the email is opened, we can retrieve the IP address from the X-Forwarded-For header, which gives us the real IP of the user who opened the email. Using the ipinfo.io API, we fetch the location details.

response = requests.get(f'https://ipinfo.io/{ip_address}/json')
location_data = response.json()
        

4. Sending Telegram Notifications

After gathering location data, we'll send a Telegram notification using the Telegram Bot API. This allows you to get real-time notifications on your Telegram channel whenever someone opens your email.

message = f"📧 Email Opened!\\nEmail ID: {decoded_email}\\nIP Address: {ip_address}"
response = requests.post(url, json=data)
        

5. Creating the Tracking Pixel

Finally, we create a tiny 1x1 pixel GIF that acts as a tracker. This pixel is embedded in the email. When the email is opened, the browser will make a request to your server, triggering the tracking logic.

pixel.write(b'\x47\x49\x46\x38\x39\x61...') # 1x1 pixel GIF

6. Deploying Your App with Docker

The Dockerfile is also provided in the github repo. To make your email tracker app production-ready, we can containerize it using Docker. Here's a simple Dockerfile for your Flask app:

FROM python:3.9.6-slim
RUN pip install -r requirements.txt
COPY . /app
WORKDIR /app
CMD ["python", "app.py"]
        

Conclusion

You now have a fully functional email tracker app that sends notifications to Telegram when an email is opened. By using Flask, Telegram Bot API, and Docker, you can easily deploy and scale this application. You can use Render.com to deploy it for free or some other hosting service. Happy tracking!

How to Get Your Telegram Chat ID

To send messages to your Telegram chat from your bot, you need to know your Telegram Chat ID. Here’s how you can find it:

  1. First, start a conversation with your bot on Telegram.
  2. Use this URL to get your chat ID from your Telegram bot: https://api.telegram.org/bot<your-bot-token>/getUpdates Replace `<your-bot-token>` with the actual token you received from BotFather.
  3. Once you visit the URL, it will return a JSON response. Look for the `chat` object, and find the `id` field. This is your chat ID.
  4. Alternatively, you can use a Telegram bot like `@userinfobot` to directly get your chat ID by typing `/start` in the bot’s chat.

Example Response

Here’s an example of what the response might look like when you visit the `getUpdates` URL:


{
  "ok": true,
  "result": [
    {
      "update_id": 123456789,
      "message": {
        "message_id": 1,
        "from": {
          "id": 987654321,
          "is_bot": false,
          "first_name": "John",
          "last_name": "Doe",
          "username": "johndoe",
          "language_code": "en"
        },
        "chat": {
          "id": 123456789, 
          "first_name": "John",
          "last_name": "Doe",
          "username": "johndoe",
          "type": "private"
        },
        "date": 1617026852,
        "text": "Hello, bot!"
      }
    }
  ]
}
    

In this example, the `chat id` is 123456789. You can use this value in your mail tracker app now.

import base64
import os
import re
from datetime import datetime
from io import BytesIO

import requests
import yaml
from flask import Flask, send_file, request, jsonify

app = Flask(__name__)
EMAIL_REGEX = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'

TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN')
TELEGRAM_CHAT_ID = os.environ.get('TELEGRAM_CHAT_ID')


def is_valid_email(email):
    return re.match(EMAIL_REGEX, email) is not None


def create_tracking_pixel():
    pixel = BytesIO()
    pixel.write(
        b'\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\x00\x00\x00\x21\xf9\x04\x01\x00\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3b')
    pixel.seek(0)
    return pixel


def send_telegram_notification(email_id, ip_address, location_data):
    url = f'https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage'
    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    message = (
        f"📧 Email Opened!\n"
        f"Email ID: {email_id}\n"
        f"IP Address: {ip_address}\n"
        f"Time: {timestamp}"
        f"\n\nLocation Data: {location_data}"
    )

    data = {
        'chat_id': TELEGRAM_CHAT_ID,
        'text': message,
        'parse_mode': 'HTML'
    }

    try:
        response = requests.post(url, json=data)
        return response.json()
    except Exception as e:
        print(f"Error sending Telegram notification: {e}")
        return None


@app.route('/<email_id>')
def track_email(email_id):
    decoded_email = ""
    try:
        email = base64.b64decode(email_id).decode('utf-8')
        decoded_email = email
        if not is_valid_email(decoded_email):
            return jsonify({"error": "Invalid email address"}), 400

    except Exception as e:
        return jsonify({"error": "Invalid email address"}), 400

    ip_address = request.headers.get('X-Forwarded-For', request.remote_addr)
    location_data = {}
    try:
        response = requests.get(f'https://ipinfo.io/{ip_address}/json')
        if response.status_code == 200:
            data = response.json()
            location_data = {
                "IP": ip_address,
                "City": data.get("city", "Unknown"),
                "Region": data.get("region", "Unknown"),
                "Country": data.get("country", "Unknown"),
                "Postal Code": data.get("postal", "Unknown"),
                "Coordinates": data.get("loc", "Unknown"),
                "ISP": data.get("org", "Unknown"),
                "Time Zone": data.get("timezone", "Unknown"),
            }
    except Exception as e:
        print(f"Error fetching location: {e}")

    location_data_yaml = yaml.dump(location_data, default_flow_style=False)
    send_telegram_notification(decoded_email, ip_address, location_data_yaml)

    tracking_pixel = create_tracking_pixel()
    return send_file(
        tracking_pixel,
        mimetype='image/gif',
        as_attachment=False
    )


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