All articles
Cloudflare WorkersChrome ExtensionSide Project

I Hid a Tiny Pixel in My Emails to Know Exactly When Someone Opens Them

How I built a privacy-first email open tracking system using Cloudflare Workers and a Chrome Extension - for $0/month

S
Samrath Reddy
·March 27, 2026·8 min read

You send a cold email for a job application. Then you wait. Did they even open it? Did it go to spam? Did they read it and ghost you, or has it been sitting unread in their inbox for three days?

Gmail has read receipts, but they only work if the recipient explicitly accepts them. Nobody does. So you're left refreshing your sent folder like a maniac, wondering whether to follow up.

I got tired of this. So I built my own solution. Here's the full thing working end-to-end:

The Concept: The Tracking Pixel

Email tracking has existed for years - it's how every marketing platform like Mailchimp and HubSpot knows when you open their newsletters. The mechanism is deceptively simple: a 1×1 transparent PNG embedded in the email body.

When your email client renders the email, it fetches all remote images - including that invisible pixel. That fetch request hits a server, which logs the timestamp and marks the email as opened. That's your read receipt.

The magic is that it's completely invisible to the recipient. They see a normal email. You see, on your end, whether they opened it and when.

One important caveat upfront: modern email providers proxy all image requests through their own servers. This means you only ever know whether the email was opened - you get no IP address, no device info, no location, nothing about the actual recipient. Just the open event and the timestamp.

Architecture: Two Components

The system has two parts that work together:

1. Cloudflare Workers (the backend)

A serverless function running on Cloudflare's edge network. It handles three endpoints:

  • GET /pixel/:emailId - serves the 1×1 tracking pixel and logs the open
  • POST /self/:emailId - called by the extension to mark a self-open (so you don't count your own opens)
  • GET /stats/:emailId - returns open events for the dashboard

All open data is stored in Cloudflare KV - a key-value store that's globally distributed and fast for reads. Each email gets a key like opens:abc123 with a JSON array of open timestamps.

When an open is detected, the Worker doesn't fire the notification immediately. Instead it pushes a job onto a Cloudflare Queue. A separate consumer Worker picks it up, waits a few seconds (to let the self-open detection from the extension catch up), and only then fires the webhook if the open is still marked as genuine. This way you never get pinged for your own opens, even if there's a race between the pixel load and the extension calling /self.

The notifications themselves go to Slack or Discord - whichever you configure. Just drop a webhook URL in your .env and you'll get a message like "someone opened your email: Subject Line" the moment a real open comes through. No polling, no dashboard-checking, just a ping straight to where you already live.

2. Chrome Extension (Manifest V3)

A service-worker-based extension that does two things:

  • Injects the tracking pixel automatically when you hit "Send" in Gmail
  • Detects self-opens by scanning the DOM when you open your own sent emails, then calling the /self endpoint

The extension uses Gmail's DOM structure to intercept the send action, generate a unique email ID, and append an <img> tag pointing to your Cloudflare Worker URL before the email goes out.

How It Works End-to-End

Here's the full flow from send to notification:

  1. You compose an email in Gmail
  2. You hit Send - the Chrome Extension intercepts this, generates a unique emailId (a random hex string), and injects <img src="https://your-worker.dev/pixel/emailId" width="1" height="1"> into the email body
  3. The email lands in the recipient's inbox
  4. They open it - their email client fetches all images, including your pixel (via a proxy)
  5. Cloudflare Worker receives the request, records { timestamp, emailId } in KV, and pushes a job to a Cloudflare Queue
  6. The Queue consumer waits a few seconds, checks if it's still a genuine open (not a self-open), then fires a webhook to your Slack or Discord
  7. You get a notification in real time - no dashboard-checking needed

The pixel endpoint:

async function handlePixel(emailId, request, env) {
  if (isBotRequest(request)) {
    return pixelResponse()
  }

  const opens = await env.OPENS_KV.get(`opens:${emailId}`, 'json') || []
  opens.push({ timestamp: Date.now() })
  await env.OPENS_KV.put(`opens:${emailId}`, JSON.stringify(opens))

  return pixelResponse()
}

function pixelResponse() {
  const pixel = new Uint8Array([
    0x47,0x49,0x46,0x38,0x39,0x61,0x01,0x00,
    0x01,0x00,0x00,0xff,0x00,0x2c,0x00,0x00,
    0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x02,
    0x00,0x3b
  ])
  return new Response(pixel, {
    headers: { 'Content-Type': 'image/gif', 'Cache-Control': 'no-store' }
  })
}

Note the Cache-Control: no-store header - critical. Without it, email clients or CDNs will cache the pixel and you'll only ever see one open event per recipient.

The Hard Part: Filtering False Positives

This is where most naive implementations fail. Raw pixel loads are full of noise:

Apple Mail Privacy Protection (MPP)

Introduced in iOS 15 and macOS Monterey, Apple Mail pre-fetches all remote images before the user even opens the email - routing requests through Apple's proxy servers. You never see the recipient's real details - just Apple's proxy making the request. This means you might see an "open" event before the person actually read the email. There's no way around this - Apple deliberately hides all client information, and that's the right call for privacy.

Gmail Image Proxy

Gmail routes all image loads through googleusercontent.com proxies. Like Apple, Google hides everything about the actual recipient. Every Gmail open looks identical from the server's perspective. You know the email was opened, but nothing else. Most major providers - Outlook, Yahoo, ProtonMail - do the same thing now.

The bottom line: the only thing you can reliably know is whether the email was opened. Any tool claiming to show you recipient location, device type, or exact identity is either making it up or working with very old data from providers that haven't proxied yet.

Bot/Scanner Detection

Security scanners from services like Microsoft SafeLinks pre-fetch images to check for malicious content. These show up as opens but are clearly automated. A simple user agent check filters most of them:

const BOT_PATTERNS = [
  /googlebot/i,
  /bingbot/i,
  /preview/i,
  /safety/i,
  /scanner/i,
]

function isBotRequest(request) {
  const ua = request.headers.get('User-Agent') || ''
  return BOT_PATTERNS.some(pattern => pattern.test(ua))
}

Self-Opens

When you open your own sent email to check it, that's not a real open. The Chrome Extension handles this by detecting when you're viewing a sent email, finding the injected pixel in the DOM, and immediately calling POST /self/:emailId to mark it as a self-view so it doesn't count.

The Dashboard

A simple web dashboard shows:

  • Per-email timeline - each open as a point on a timeline with timestamp
  • Calendar heatmap - GitHub-style grid showing which days people read your emails
  • Peak hours chart - bar chart of opens by hour of day, so you can optimize when you send

Since all you're storing is open timestamps per email, the data is minimal and the KV reads are fast.

A Cloudflare Cron Trigger runs daily to clean up open data older than 90 days, keeping storage tidy within the free tier limits.

Running for Free

Cloudflare's free tier is remarkably generous:

FeatureFree Tier Limit
Worker requests100,000/day
KV reads100,000/day
KV writes1,000/day
KV storage1 GB
Cron TriggersIncluded

For personal use - even heavy personal use - this is more than enough. I send maybe 20-30 tracked emails a day. Even if each one gets opened 10 times, that's 200-300 pixel loads per day. The free tier handles this with room to spare.

Total monthly cost: $0.

What I Learned

KV is eventually consistent. Cloudflare KV has eventual consistency across regions - a write in one data center might not be immediately visible in another. For open tracking this is fine, but worth knowing.

You can't fight the proxy. I spent time early on trying to work around email provider proxies to get more recipient data. That's a dead end. Every major provider proxies image requests now, and that's good for user privacy. The right approach is to accept that you only know whether an email was opened and build around that constraint.

Try It Yourself

The full source is on GitHub at github.com/samrathreddy/mail-tracker.

Setup takes about 5 minutes:

  1. Clone the repo
  2. wrangler deploy to push the Worker to Cloudflare
  3. Load the unpacked Chrome Extension in chrome://extensions
  4. Configure your Worker URL in the extension settings

No accounts, no subscriptions, no data leaving your control. Your open data lives in your own Cloudflare KV namespace.


The whole project came together in a weekend. The hardest part wasn't the code - it was accepting that you simply can't know as much as you'd like about the other end. Modern email privacy protections make that intentionally difficult, and honestly, they should. All you need to know is whether someone opened it. That's enough to decide whether to follow up.

If you build on top of this or have ideas for improvements, open an issue on GitHub.