Stop Trusting Ghost Opens: A Guide to Email Tracking Pixels
Thu Apr 23 2026
You send an email. Your dashboard says someone opened it. You feel good.
Most of that data is a lie.
In 2026, privacy features from Apple and Google flood your database with false positives. If your open rate looks suspiciously high, you're seeing machine opens not human interest.
Tracking isn't magic. It's a 43-byte transparent image. This guide explains how it works, why it breaks, and how we filter the noise at Varnan.
How a Tracking Pixel Works
A tracking pixel is an <img> tag at the bottom of your email. It loads a 1x1 transparent GIF from your server.
We use GIFs because they're small. An optimized 1x1 GIF takes 43 bytes. When you send millions of emails, every byte counts.
Here's the Base64 for that image:
R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
When you send an email, you add this tag:
<img src="https://links.varnan.tech/api/track?id=cmoabzfvk0003f24gz" width="1" height="1" alt="" style="display:none;" />
The id matches a row in your database. Use a custom domain like links.varnan.tech. Shared domains get flagged as spam. A custom domain protects your sender reputation.
The Request Lifecycle
- The Send: Your server creates a unique ID and injects the tag.
- The Inbox: The email sits unread. Nothing happens.
- The Open: A human clicks the email. Their client renders the HTML.
- The Fetch: The client requests the image from your server.
- The Capture: Your server gets the request, logs the ID, and sends back the GIF.
This worked for years. Then the big providers changed the rules.
The Privacy Wall: Apple and Google
Modern email clients sit between you and the reader. They fetch images before the user sees the email.
Google Image Proxy (GGPHT)
Gmail routes image requests through its own servers. Google fetches your image once and caches it. If the user opens the email again, they load it from Google's cache, you never see the second open.
Google also pre-fetches images in the background. Your server logs an open before the human has looked at their phone.
Apple Mail Privacy Protection (MPP)
Apple downloads every image in every email in the background, regardless of whether the user opens the app. If you send 1,000 emails to Apple Mail users, you'll see close to 1,000 opens. It's noise.
The IP Blocking Trap
When we built our tracker, we saw this in the logs:
[Tracking Hit] ID: cmoabzfvk0003f24gz | UA: Mozilla/5.0 (Windows NT 10.0; Win64; x64) (via ggpht.com GoogleImageProxy) | IP: 66.249.92.140
We tried to block these IPs. We checked them against Google and Apple datacenter ranges. If a request came from a datacenter, we ignored it.
We were wrong.
When a human opens Gmail on their phone, Google routes that request through the same proxy IPs. By blocking the proxy range, we blocked real users. You can't use IP addresses to separate bots from humans here.
How We Actually Filter Bots
Two signals work. IP ranges don't. Here's what does.
Signal 1: User-Agent Strings
Your logs already have this. Look at the User-Agent header on every tracking request.
Proxies and prefetch systems identify themselves:
# Google Image Proxy
"Mozilla/5.0 ... (via ggpht.com GoogleImageProxy)"
# Apple MPP
"Mozilla/5.0 ... Macintosh ... AppleWebKit ... (like Gecko)"
# combined with an Apple datacenter IP — the MPP signature
# Microsoft SafeLinks (Outlook's link scanner)
"Mozilla/5.0 ... ms-office"
# Generic security gateway
"Mozilla/5.0 ... SecurityScan"
You can filter on partial string matches. If the User-Agent contains GoogleImageProxy, ms-office, or SecurityScan — it's a machine. Log it separately, don't count it as a human open.
This catches a large chunk of false positives before you need anything else.
Signal 2: The 10-Second Rule
User-Agent filtering doesn't catch everything. Apple MPP uses rotating IPs and sometimes generic browser strings. For those, timing is the filter.
Bots are fast. They download images the moment an email hits the server. Humans are slow. A person has to see a notification, unlock their phone, and tap the email. That takes time.
Ignore any open that happens within 10 seconds of the send. These are machine opens.
The Node.js Implementation
This code runs on our Vercel backend. It combines both filters, User-Agent matching first, then the timing check and uses cache-busting headers to force fresh fetches from proxies.
// api/track.js
const prisma = require('./_lib/prisma');
const PIXEL_BUFFER = Buffer.from(
'R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==',
'base64'
);
// Patterns that identify machine/proxy requests
const BOT_UA_PATTERNS = [
'GoogleImageProxy',
'ggpht.com',
'ms-office',
'SecurityScan',
'preview',
'PreviewAgent',
];
function isBotUserAgent(ua = '') {
return BOT_UA_PATTERNS.some(pattern =>
ua.toLowerCase().includes(pattern.toLowerCase())
);
}
module.exports = async function handler(req, res) {
try {
const emailId = req.query.id;
const userAgent = req.headers['user-agent'] || '';
if (emailId) {
const scheduledEmail = await prisma.scheduledEmail.findUnique({
where: { id: emailId }
});
if (scheduledEmail) {
const timeSinceSent = Date.now() - new Date(scheduledEmail.updatedAt).getTime();
const isBot = isBotUserAgent(userAgent);
const isTooFast = timeSinceSent < 10000;
if (isBot) {
console.log(`[Filtered] Bot UA detected: ${userAgent}`);
} else if (isTooFast) {
console.log(`[Filtered] Too fast — likely machine open (${timeSinceSent}ms)`);
} else {
await prisma.scheduledEmail.update({
where: { id: emailId },
data: {
opens: { increment: 1 },
lastOpenedAt: new Date()
}
});
}
}
}
// Force proxies to fetch fresh data every time
res.setHeader('Content-Type', 'image/gif');
res.setHeader('Content-Length', PIXEL_BUFFER.length);
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
res.setHeader('Surrogate-Control', 'no-store');
return res.end(PIXEL_BUFFER);
} catch (error) {
console.error('Track pixel error:', error);
res.setHeader('Content-Type', 'image/gif');
res.setHeader('Content-Length', PIXEL_BUFFER.length);
return res.end(PIXEL_BUFFER);
}
};
The Cache-Control headers tell proxies not to store the image. This forces them to hit your server on every open, which matters if you want to track reopens, not just first opens.
What to Track Instead of Opens
Open rates are a health check, not a success metric. Here's the hierarchy:
Replies: the most reliable signal for cold outreach. If someone replies, they read it. No proxy involved.
Link clicks: track these separately with the same ID-based approach. A click is a higher-intent signal than an open, and it bypasses the Apple/Google prefetch problem entirely because clicking a link requires a human.
Time-to-reply: if your tool logs when the email was sent and when the reply arrived, you can correlate that with which subject lines or sequences convert. Opens can't tell you that.
Set your open rate threshold at around 30–40% for a healthy campaign. If you're seeing 70%+, you're counting machines.
Common Questions
Does the 10-second rule catch every bot?
No. Some enterprise security gateways scan emails minutes after delivery. But this rule removes the flood of false positives from Apple and Google, which make up the majority of ghost opens.
Will I miss real opens from fast readers?
Unlikely. It's hard to open an email in under 10 seconds from the moment it's sent. Most people take 15–20 seconds minimum notification, unlock, tap, render. If you're nervous, push the threshold to 5 seconds. You'll still catch most bots.
Should I use CSS tracking instead of a pixel?
Don't. Spam filters flag invisible CSS elements and deceptive display tricks. A 1x1 GIF is the standard. Stick to it.
What about security gateways that scan links?
These show up in your click tracking too, not just opens. Apply the same timing filter if a link is clicked within 3–5 seconds of send, it's a scanner. Real clicks take longer.
This Is What We Use for Varnan's Outreach
We built this tracker to run our own cold outreach campaigns the ones we use to reach YC founders and post-PMF AI startups. We needed accurate open data to know which subject lines were actually working, not inflated numbers from Apple's prefetch.
If you're running outreach at scale and want to talk about what's working on the GTM side, email paras@varnan.tech or book 30 minutes.