Email tracking
Goal: track email opens for gog gmail send via a tiny tracking pixel served from a Cloudflare Worker.
High-level:
gog gmail send --trackinjects a 1×1 image URL into the HTML body.- The Worker receives the request, stores an “open” row in D1, and returns a transparent pixel.
gog gmail track opens …queries the Worker and prints opens.
Abuse controls:
- Repeated opens for the same tracking id, IP, and user-agent are deduplicated within a one-hour window.
- Each IP can record at most 100 opens per hour; excess requests still receive the transparent pixel but are not inserted into D1.
Privacy note:
- Tracking is inherently sensitive. Treat this as instrumentation you opt into per email.
- The Worker stores recipient email, subject hash, sent/open timestamps, IP, user-agent, bot classification, and coarse geo from Cloudflare request metadata when available.
- The deployed Worker includes a daily cron trigger that deletes open rows older than 90 days.
- Admin
/opensqueries default to 100 rows and are capped at 500 rows per request.
#Setup (local)
Create per-account tracking config + keys:
gog gmail track setup --worker-url https://gog-email-tracker.<acct>.workers.dev
This writes a local config file containing:
worker_url(base URL)- the active tracking key version
- per-account tracking/admin keys are stored in your keychain/keyring (not in the JSON file)
Optional: auto-provision + deploy with wrangler:
gog gmail track setup --worker-url https://gog-email-tracker.<acct>.workers.dev --deploy
Flags:
--worker-name: defaultgog-email-tracker-<account>.--db-name: default to worker name.--worker-dir: defaultinternal/tracking/worker.
Re-run gog gmail track setup any time to re-print the current TRACKING_KEY / ADMIN_KEY values (it’s idempotent unless you pass explicit --tracking-key / --admin-key).
#Deploy (Cloudflare Worker + D1)
From repo root:
cd internal/tracking/worker
pnpm install
Provision secrets (use values printed by gog gmail track setup):
pnpm exec wrangler secret put TRACKING_KEY
pnpm exec wrangler secret put TRACKING_KEY_V1
pnpm exec wrangler secret put TRACKING_CURRENT_KEY_VERSION
pnpm exec wrangler secret put ADMIN_KEY
Create D1:
pnpm exec wrangler d1 create gog-email-tracker
Update wrangler.toml to reference the D1 database_id, then migrate and deploy:
pnpm exec wrangler d1 execute gog-email-tracker --file schema.sql --remote
pnpm exec wrangler deploy
wrangler.toml includes a daily cron trigger for retention cleanup. After deploy, Cloudflare calls the Worker once per day and the Worker deletes open rows older than 90 days.
#Rotate tracking keys
Rotate the pixel encryption key without invalidating old tracking ids:
gog gmail track key rotate
The command generates the next key version, deploys all active TRACKING_KEY_V<N> secrets plus TRACKING_CURRENT_KEY_VERSION, then stores the new current version in local config. Legacy unversioned tracking ids still decrypt through the stored TRACKING_KEY fallback.
For local-only testing:
gog gmail track key rotate --no-deploy
Do not send newly tracked mail after --no-deploy until the Worker has the matching versioned secret, or new pixels will not decrypt.
#Send tracked mail
Tracked email constraints:
- Exactly one recipient (
--to; no cc/bcc). - HTML body required (
--body-html).
Optional per-recipient sends:
gog gmail send \
--to a@example.com,b@example.com \
--subject "Hello" \
--body-html "<p>Hi!</p>" \
--track \
--track-split
--track-split sends separate messages per recipient (no CC/BCC; each message has a unique tracking id).
Example:
gog gmail send \
--to recipient@example.com \
--subject "Hello" \
--body-html "<p>Hi!</p>" \
--track
#Query opens
By tracking id:
gog gmail track opens <tracking_id>
By recipient:
gog gmail track opens --to recipient@example.com
Status:
gog gmail track status
#Troubleshooting
required: --worker-url: rungog gmail track setup --worker-url …first (or pass--worker-urlagain).401/403on/opens: admin key mismatch; redeploy secrets and re-runtrack setupif needed.- New tracked messages do not show opens after key rotation: verify the Worker has
TRACKING_KEY_V<N>for the current localgmail track statusversion andTRACKING_CURRENT_KEY_VERSIONmatches it. - No opens recorded:
- ensure the HTML body contains the injected pixel (view “original” in your mail client).
- some clients block images by default; “open” only happens after images load.