Self-open suppression
It does not turn green when you look at your own Sent folder.
This is the hardest problem in the product and the one that justifies not having a Gmail scope. When you open a sent message, your Gmail fetches the tracking pixel through the same proxy a recipient would use. Same user agent, same IP, same referrer. Nothing in the request distinguishes "they read it" from "you looked at Sent".
- +0.6 s Your own view Pixel loaded Your own Gmail loaded the image while showing the copy in your Sent folder. Who: You.
- +9 min Your own view Pixel loaded You were viewing this message in Gmail at the time, so this is your own open. Who: You.
- +2 h Opened (proxied) Pixel loaded Gmail loaded the image through its proxy while showing the message. Gmail hides the reader's device and location. Who: Gmail hides who read it. The recipient — or you, if you opened this on a device without MailSignals.
The problem every tracker has
Without a fix, every sender watches their own checkmarks turn green seconds after opening Sent. It is the most common complaint about every tracker on the market, and it destroys trust in every other number the product shows.
Commercial trackers solve it with the Gmail API: one has a patented mechanism that replaces the copy in your Sent folder with a pixel-free copy. MailSignals has no API access, so it needs a different idea.
The insight
We cannot tell the two apart from the proxied request. But we know two things the request does not say: the extension is running in your browser and can see what is on screen, and the message was registered from your own network. The first covers the proxied fetch; the second covers the direct one.
The content script reports a view only for messages you sent, only while the message is expanded and the tab is visible, on load, on every expansion, on visibility change and on a 20-second heartbeat. Reports are batched, and each lookup is scoped to your account, including the tokens the extension supplies, so nobody can suppress anyone else's opens by claiming to be looking at them.
Five paths, because the race has several outcomes
The pixel and the view signal race, and either can win; and your Gmail fetches the pixel two different ways.
The view signal lands first
The tracking endpoint reads your recent view signals before classifying. Rule
self-view-correlationfires and the hit is judged yours on the spot, at confidence 0.97. Nothing to undo.The pixel lands first
The common case: Gmail requests the image while rendering, before the extension's report arrives. The open is already counted, so the views endpoint, after storing the signal, withdraws every counted open within 20 seconds of it. Without this pass every sender sees their checkmarks turn green a second after opening Sent.
Both in flight at once
The pixel handler reads view signals before it commits the event, so a signal arriving during that write is invisible to it, and equally invisible to the views endpoint, which finds no event yet to reclassify. Both halves miss each other. The Firestore trigger runs after the event is committed, which makes it the one place that can see both; it re-checks, withdraws the open, and returns before any rollup or alert sees it.
Your Sent copy rendered directly
Gmail web sometimes fetches the pixel directly, not through the proxy, when it renders the copy you just composed. Rule
self-sent-copycatches it by comparing the hit's salted network hash with the one stored at registration. No view signal is involved, so it works before the extension's first report.Neither: you say so
Reading your own sent mail on a phone with no extension produces a proxied self-open that nothing can correlate. "That open was me" in the dashboard (by event, or all proxied opens at once) or in the Gmail sidebar reclassifies it, keeps the original verdict for audit, and can be undone with "That was not me after all". Each claim is counted; at two, the dashboard explains that you read your sent mail on a device without MailSignals and points at Gmail's "Ask before displaying external images" setting, the only thing that removes the ambiguity at the source.
Withdrawal rebuilds, it does not subtract
Withdrawing an open changes the event's classification to "your own view", then rebuilds the open buckets and the first and last open times from every open event as it will be after the transaction. Rebuilding replaced add-and-subtract bookkeeping, which drifted the moment two reclassifications raced. The original verdict is preserved, which makes the change auditable and the correction reversible.
The selector re-evaluates what counts at your current sensitivity, so only opens that count today are candidates.
The honest gap
| Where you read your sent mail | What happens |
|---|---|
| Desktop Gmail in Chrome or Edge with the extension | Withdrawn automatically, by whichever of the four automatic paths applies |
| Gmail web on another browser or profile without the extension, on the same network | Caught only when Gmail fetches directly with your network hash; a proxied fetch is counted until you claim it |
| The Gmail mobile app | Counted as a proxied open until you claim it. Set images to "Ask before displaying external images" on the phone and the fetch never happens |
| A recipient on your own network reading in Gmail web with direct image loading | Judged as your own. The documented cost of the network rule |
Where it lives
extension/src/gmail/messages.ts- Reporting views from Gmail: expansion, visibility, the heartbeat.
functions/src/api/views.ts- The endpoint, ownership scoping, the backward pass.
functions/src/tracking/reclassify.ts- Withdraw and restore; rebuilding the counters from events.
functions/src/triggers.ts- The backstop that runs after the event is committed.
shared/src/classifier.ts- Rules
self-sent-copyandself-view-correlation.
Related: the guide on self-opens, for any tracker · the rule table.
Run it in your own project.
One Firebase project, two subdomains, one deploy script. About an hour the first time, and no subscription afterwards.