A simple, self-hosted email tracking pixel and link analytics service.
- Open Tracking: Invisible 1×1 pixel to track email opens
- Link Tracking: Secure click-through redirect with open-redirect protection
- Modern Dashboard: Responsive UI with search, inline detail drawer, charts, and pagination
- Rich Analytics: Geo-location, ISP/ASN, device, browser, OS, and full HTTP header fingerprint
- Security: Timing-safe API key auth, HMAC-signed webhooks, HSTS, CSP, rate limiting
- Privacy First: Self-hosted — you own the data
- Multi-Node Sync: Optional pull-based sync from edge nodes to a central server
pip install -r requirements.txt
python manage.py init_all
python server.py # http://localhost:8080Set DEBUG=true if you want auto-generated ephemeral keys (printed in the console).
For comprehensive guides on setup, deployment, development, testing, and more, please reference our official Wiki (located in the docs folder):
- 🏠 Wiki Home
- 🚀 Setup Guide: Local installation, requirements, and environment variables.
- 🌍 Deployment Guide: How and where to deploy naarad (Render, Railway, VPS), DB backups, scaling, and maintenance.
- 🛠️ Architecture & Development: Frontend/Backend codebase overview, logic flow, and contribution guide.
- 🧪 Testing Guide: Testing across email clients, localhost networking, and ngrok.
- 🚑 Troubleshooting: Fixes for common errors, port issues, and email caching.
For a detailed walkthrough, see the Deployment Guide.
- Follow the Quick Start above.
- Server binds to
0.0.0.0:8080by default. Openhttp://<YOUR_IP>:8080from any device on your network.
- Push this repository to GitHub and connect it to your PaaS (Platform as a Service).
- Add a PostgreSQL or MySQL database for production stability.
- Configure the Environment Variables (check Setup.md for a full list). CRITICAL:
SECRET_KEYandAPI_KEY. - Deploy. The
Procfileand backend logic handle the auto-migrations upon launch.
| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
HTTP listen port |
DEBUG |
false |
Enable debug mode (auto-generates ephemeral keys) |
DATABASE_URL |
(none) | PostgreSQL connection string. Uses SQLite if unset. |
SECRET_KEY |
required in prod | Flask session signing key |
API_KEY |
required in prod | Dashboard / API authentication key |
CORS_ORIGINS |
* |
Allowed CORS origins (comma-separated) |
TRUSTED_PROXY_COUNT |
0 |
Number of reverse proxies in front of the app |
WEBHOOK_URL |
(none) | URL to POST open/click events to |
WEBHOOK_SECRET |
(none) | HMAC-SHA256 signing key for webhook payloads |
GEO_API_URL |
http://ip-api.com/json/{ip} |
Geo-lookup endpoint template |
RATE_LIMIT_PER_MINUTE |
60 |
Max tracking requests per IP per minute |
API_RATE_LIMIT_PER_MINUTE |
120 |
Max API requests per IP per minute |
SYNC_REMOTE_URL |
(none) | Remote Naarad node URL for pull-sync |
SYNC_API_KEY |
(none) | API key for the remote sync node |
SYNC_INTERVAL |
300 |
Seconds between sync cycles |
SYNC_AUTO_WIPE |
false |
Delete synced records from the remote after pull |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/track?id=… |
— | Serve 1×1 tracking pixel |
GET |
/click/<id>/<url> |
— | Record click and redirect |
GET |
/dashboard |
— | Dashboard HTML |
GET |
/api/health |
— | Health check (DB status) |
GET |
/api/stats |
✔ | Aggregated statistics |
GET |
/api/tracks |
✔ | List tracked pixels |
POST |
/api/track |
✔ | Create a new pixel |
GET |
/api/track/<id> |
✔ | Pixel detail + click history |
PUT |
/api/track/<id> |
✔ | Update label / metadata |
DELETE |
/api/track/<id> |
✔ | Delete pixel and its data |
GET |
/api/export |
✔ | CSV / JSON export |
GET |
/api/sync/status |
✔ | Sync configuration status |
POST |
/api/sync |
✔ | Trigger manual sync |
Auth = X-API-Key header required.
See docs/Troubleshooting.md for solutions to common issues (e.g., port conflicts, 401 Unauthorized, database locking, or pixel caching in Gmail).