mail-tracker / main.py
vivekchakraverty's picture
Upload folder using huggingface_hub
a8027c6 verified
Raw
History Blame Contribute Delete
4.05 kB
"""Mail Tracker — the public half of Mr AI Marketer's email open/click tracking.
Serves a 1x1 pixel for open tracking and a redirect for click tracking, both
reachable from anywhere (a recipient's mail client is never on the same machine
as the desktop app that sent the mail), plus a secret-gated /events endpoint the
local app polls to pull new hits back down. Storage is a local SQLite file on
this Space's own container disk — survives normal sleep/wake, but not a factory
reboot or a redeploy (rebuilding the image wipes it), an accepted tradeoff for a
free, personal-scale tool; the local app is the durable copy of record.
SYNC_SECRET is a single value shared by every installation of the app (there is
no per-user boundary on a shared public Space), so it only deters casual
scraping of /events, not a determined reader of the shipped app's own source —
the real privacy boundary is client-side: the syncing app discards any event
whose token it doesn't recognize as its own.
"""
from __future__ import annotations
import base64
import os
import sqlite3
from contextlib import contextmanager
from datetime import datetime, timezone
from pathlib import Path
from typing import Iterator
from fastapi import FastAPI, Query
from fastapi.responses import JSONResponse, RedirectResponse, Response
from urllib.parse import urlparse
DB_PATH = Path(__file__).parent / "tracker.sqlite3"
SYNC_SECRET = os.environ.get("SYNC_SECRET", "")
# The standard minimal 1x1 GIF used by countless open-source tracking pixels —
# decoded from a literal already verified to be a well-formed 34-byte GIF89a
# (header + 1x1 logical screen + 2-color table + one image block + trailer).
_PIXEL_GIF = base64.b64decode("R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==")
_NO_STORE_HEADERS = {
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
"Pragma": "no-cache",
"Expires": "0",
}
app = FastAPI()
def _now_iso() -> str:
return datetime.now(timezone.utc).isoformat()
@contextmanager
def _connect() -> Iterator[sqlite3.Connection]:
conn = sqlite3.connect(DB_PATH, timeout=15)
conn.row_factory = sqlite3.Row
try:
yield conn
conn.commit()
finally:
conn.close()
def _init_db() -> None:
with _connect() as conn:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
token TEXT NOT NULL,
type TEXT NOT NULL,
url TEXT,
occurred_at TEXT NOT NULL
)
"""
)
_init_db()
def _log_event(token: str, event_type: str, url: str | None = None) -> None:
with _connect() as conn:
conn.execute(
"INSERT INTO events (token, type, url, occurred_at) VALUES (?, ?, ?, ?)",
(token, event_type, url, _now_iso()),
)
@app.get("/health")
def health() -> dict:
return {"status": "ok"}
@app.get("/o/{token}.gif")
def open_pixel(token: str) -> Response:
_log_event(token, "open")
return Response(content=_PIXEL_GIF, media_type="image/gif", headers=_NO_STORE_HEADERS)
@app.get("/c/{token}")
def click_redirect(token: str, u: str = Query(...)):
parsed = urlparse(u)
if parsed.scheme not in ("http", "https") or not parsed.netloc or len(u) > 2000:
return JSONResponse({"error": "invalid destination url"}, status_code=400)
_log_event(token, "click", url=u)
return RedirectResponse(url=u, status_code=302, headers=_NO_STORE_HEADERS)
@app.get("/events")
def events(since_id: int = 0, secret: str = "", limit: int = 500):
if not SYNC_SECRET or secret != SYNC_SECRET:
return JSONResponse({"error": "forbidden"}, status_code=403)
limit = max(1, min(limit, 500))
with _connect() as conn:
rows = conn.execute(
"SELECT id, token, type, url, occurred_at FROM events WHERE id > ? ORDER BY id ASC LIMIT ?",
(since_id, limit),
).fetchall()
return {"events": [dict(row) for row in rows]}