A self-hosted email open tracker: create a tracked email on the dashboard, get a unique tracking-pixel image URL, paste it into your Gmail, and see opens the second they happen. No branding, no "sent with" signature, no third party — your server, your data.
| 👁️ Open tracking | Invisible 1×1 pixel per email — open count, timestamps, per-open IP & device/app details |
| 🖱️ Link click tracking | Add links when creating a tracked email; use the generated /c/… URLs as hyperlinks to log clicks |
| 🔔 Telegram notifications | Instant alerts for opens, link clicks and follow-up reminders — configure in ⚙️ Settings |
| ⏰ Follow-up reminders | Set "remind me in N days if unopened" per email; a daily cron ping sends you a Telegram nudge |
| 📊 Smart dashboard | Stats cards, live search, All / Opened / Unopened filters, CSV export |
| 🌙 Dark mode | Toggle in the header, remembered per browser |
- You create a tracked email on the dashboard (recipient + subject).
- You get a unique 1×1 invisible tracking-pixel image URL.
- In Gmail compose: Insert photo (🖼️) → Web address (URL) → paste the URL → Insert (set size to Small).
- When the recipient opens the mail, their mail app loads the image — the open is logged with timestamp, IP and device info, and your Telegram pings instantly ⚡.
- Sign up at pythonanywhere.com (free Beginner account).
- Dashboard → Consoles → start a Bash console, then run:
git clone https://github.com/nuvishh/MailPulse.git cd MailPulse mkvirtualenv --python=/usr/bin/python3.11 mailpulse pip install -r requirements.txt - Go to the Web tab → Add a new web app → choose Flask, Python 3.11, path
/home/<your-username>/MailPulse. - In the Web tab, open the WSGI configuration file and replace its whole contents with this (put your username and a strong password):
import os import sys path = '/home/<your-username>/MailPulse' if path not in sys.path: sys.path.insert(0, path) os.environ['DASHBOARD_PASSWORD'] = 'choose-a-strong-password' from app import app as application
- Hit Reload on the Web tab.
- Done — your tracker is live at
https://<your-username>.pythonanywhere.com. Dashboard login with the password you set above.
Free web apps need an occasional renewal — PythonAnywhere emails you, just log in and extend it.
Alternative: Deploy on Render (free)
- Push this folder to a GitHub repo.
- Go to render.com → New + → Web Service → connect the repo — or use the
render.yamlin this repo via New + → Blueprint. - Render auto-generates a
DASHBOARD_PASSWORD(seerender.yaml) — find it under Environment after deploy. - Deploy. Your public URL will look like
https://mail-tracker-xxxx.onrender.com.
Note: Render's free tier sleeps after inactivity (cold starts ~30s) and its disk is ephemeral — the SQLite DB is wiped on redeploys.
- Chat with @BotFather on Telegram →
/newbot→ copy the bot token. - Start a chat with your new bot (tap Start).
- Open
https://api.telegram.org/bot<token>/getUpdates, find"chat":{"id":…}— that's your chat ID. - In MailPulse go to ⚙️ Settings, paste both, save, and hit Send test message.
You can also set TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID as environment variables instead of using the settings page.
Free hosting can't run background jobs, so an external cron pings the app:
- When creating a tracked email, set Follow-up reminder to e.g.
3days. - Create a free account at cron-job.org (no card) and add a daily job hitting:
https://<your-url>/api/check-followups?key=<url-encoded-dashboard-password>⚠️ If your password contains#,&or+, URL-encode it (#→%23) — otherwise the key never reaches the server and the endpoint returns 403 silently. The Settings page shows the ready-to-copy encoded URL. - If a tracked email is still unopened after N days, you get a Telegram reminder — once per email.
| Variable | Purpose | Default |
|---|---|---|
DASHBOARD_PASSWORD |
Password for the dashboard | (none = open) |
TELEGRAM_BOT_TOKEN |
Telegram bot token (or set in ⚙️ Settings) | (none) |
TELEGRAM_CHAT_ID |
Telegram chat ID (or set in ⚙️ Settings) | (none) |
PORT |
Port to listen on | 5000 |
DB_PATH |
SQLite file location | ./tracker.db |
pip install -r requirements.txt
DASHBOARD_PASSWORD=secret python app.py
# open http://localhost:5000 (pixel URLs will use localhost — for real
# tracking you need the public URL)Flask · SQLite · Jinja2 · vanilla JS · Telegram Bot API — no build step, no framework bloat. One app.py, runs anywhere Python runs.
- Image blocking: if the recipient's mail app blocks images, the open won't register. Reliable in most cases, but not 100%.
- Gmail image proxy: Gmail loads images through its own proxy — the open is still recorded, but the IP you see will be Google's, not the recipient's. Timestamps remain accurate.
- Apple Mail Privacy Protection may pre-load images and cause false opens.
- SQLite on ephemeral disks (e.g. Render free): the DB survives restarts but is wiped on redeploys. PythonAnywhere's disk persists.
Built because Mailtrack's free plan stamps "Sent with Mailtrack" on every mail — unprofessional on a job application. This stamps nothing.