In today’s email environment, marketing platforms often bundle “open” or “read” tracking by default. but if you want one personal Tracking pixel system—where you can view a log of activations for certain one-time emails you send mailbox?
GitHub link: Email tracker
Roll by yourself Node.js + Express + EJS + SQLite app:
- produces 1×1 pixels Bind to a unique ID,
- Record opening event (date/time, IP, user agent),
- Provide real .png files This way Gmail/other clients will see the standard image.
You get a lightweight dashboard showing which pixels were created, as well as a log of open events. While this isn’t 100% foolproof (due to image blocking or proxies like GoogleImageProxy), it’s a fun and educational way to learn about basic open tracking practices for personal emails.
1. Prerequisites
- Node.js Install locally.
- have a certain degree of familiarity Express (for routing).
- willing to tinker Agers (template engine) and SQLite Used to store data.
hint: If you want others (external recipients) to open your Pixel, your server must Can be visited by outsiders. Hosted on similar platforms make it, Herokuor use a tunneling service (e.g. ngrok or local tunnel) can help.
2. Core concepts
2.1.Tracking Pixels
A “tracking pixel” is simply a hidden image in an email. If the recipient’s client loads the image, your server will see an HTTP request for the 1×1 PNG (or GIF).
2.2. Logging is turned on
when someone (or some agent) gets /tracker/
you record:
- Timestamp (when it is acquired),
- IP address,
-
user agent (may show
GoogleImageProxy
For Gmail, it means that the real user’s IP is blocked).
2.3. Gmail and Image Proxy
Modern Gmail proxy image (by ggpht.com
or similar). This means:
- you won’t see recipient’s Real IP address.
- Gmail often gets pixels once and caches it so subsequent opens may not show up as new logs.
Despite these limitations, you can still see if/when The image is first fetched—usually around the time a user first opens your email.
3. Our simple node application
3.1.Project settings
Create a folder (e.g. mail-tracker
), Then:
cd mail-tracker
npm init -y
npm install express ejs sqlite3 uuid
you will end up with a package.json
Reference these dependencies. Next:
-
create one
public/images
Folder and 1×1 transparent PNG namedpixel.png
. -
create Two EJS files in one
views
Folder:index.ejs
(dashboard) andlogs.ejs
(show log).
3.2. app.js
(main server file)
The following is a simplified excerpt. it:
- Store pixel data in SQLite (
mail-tracker.db
). -
Serve static files (so
pixel.png
can be loaded if we need). - There is one
/tracker/:id.png
Recording is turned on and the real route is sent backpixel.png
.
const express = require('express');
const path = require('path');
const { v4: uuidv4 } = require('uuid');
const sqlite3 = require('sqlite3').verbose();
const app = express();
app.set('view engine', 'ejs');
app.use(express.urlencoded({ extended: false }));
// Serve static files from /public
app.use(express.static(path.join(__dirname, 'public')));
// Initialize SQLite
const db = new sqlite3.Database(path.join(__dirname, 'mail-tracker.db'), (err) => {
// Create tables if not existing
});
// Middleware to get baseUrl for EJS
app.use((req, res, next) => {
const protocol = req.protocol;
const host = req.get('host');
res.locals.baseUrl = `${protocol}://${host}`;
next();
});
// Dashboard: list all pixels
app.get('/', (req, res) => {
// SELECT * FROM pixels ...
res.render('index', { pixels });
});
// Create pixel
app.post('/create', (req, res) => {
const pixelId = uuidv4();
// Insert pixel into DB
// redirect to "https://dev.to/"
});
// The tracker route
app.get('/tracker/:id.png', (req, res) => {
// Check pixel ID, log open in DB
// Serve the real 'pixel.png'
res.sendFile(path.join(__dirname, 'public', 'images', 'pixel.png'));
});
// View logs
app.get('/logs/:id', (req, res) => {
// SELECT logs from DB for that pixel
res.render('logs', { pixel, logs });
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Listening on ${PORT}`));
3.3. index.ejs
(dashboard)
Displays a form for creating new pixels and a table listing existing pixels:
Mail Tracker - Dashboard
charset="UTF-8" />
Mail Tracker - Dashboard
Create a New Tracking Pixel
/>
Existing Pixels
3.4. logs.ejs
(show log)
List each open event (time, IP, user agent). We can format the time, group quick logs with colors, etc:
Mail Tracker - Logs
charset="UTF-8" />
Logs for <%= pixel.name %>
Created At: <%= pixel.createdAt %>
Open Events
style="font-style: italic;">
You may see extra logs if bots (for example, GoogleImageProxy via ggpht.com) or the email client repeatedly load the image.
Check each log’s timestamp to distinguish real user opens from proxy fetches.
Time IP User-Agent
<% // For each log, show the local-time date, IP, userAgent, etc. %>
4. Embed Gmail
4.1. Copy the tracker URL
Once your server is publicly available, your pixel code will have the following URL:
https://myapp.example.com/tracker/1234abcd-....png
Or if you use native tunneling:
https://random.loca.lt/tracker/1234abcd-....png
4.2. Insert photos via URL
- write New email in Gmail.
- Click Insert photo → URL.
- paste Tracking URL.
- If Gmail says “We can’t find or access this image,” you can still insert and send it.
- When the recipient opens (and the image is enabled), Gmail loads (or caches) the image, and an event is recorded in your logs.
notes: Since Gmail proxies images, the request may come from a Google server (e.g.
GoogleImageProxy/ggpht.com
). You won’t get the real recipient’s IP. but you did see when The pixel is retrieved – usually associated with user activation.
5. Understand “extra logs” and agent behavior
- Multiple records: If Gmail or other clients refresh or re-fetch the image, or the user reopens the mail, you will see multiple entries. Some may be in Second Triggered by background processes or spam filters.
-
user agent: you may see
GoogleImageProxy
Rather than the actual browser user agent. This is normal for Gmail. Other clients (such as Apple Mail or Outlook) may display more direct messages. - intellectual property: Due to proxies, you will often see the Google IP range instead of the actual recipient’s IP. This is a privacy measure.
6. Limitations and future enhancements
- Image occlusion: If the recipient’s mail client blocks images by default, your pixel will never be loaded. So you won’t see open events.
- cache: Gmail special cache image. Subsequent openings may not trigger new requests.
- No IP/location: When using a proxy, you cannot see the real user’s IP or location.
-
unique query parameter: If you want to track a single recipient, create a separate pixel or additional query string for each person, e.g.
?user=john@example.com
.
7. Why build a personal tracker?
- educational: Learn how open tracking works behind the scenes.
- privacy: You control your own data rather than trusting third-party marketing providers.
- debug: If you want to see if a friend/customer is reading your email, or just confirm that some one-time outreach is on.
(Always pay attention legal or moral Restrictions between your region and your contacts.
8. Conclusion
Running your own open tracking service can be a fun side project, especially knowing how mailbox or other providers to process images. you will soon find out Google Image Agency The real IP address can be masked, and if the image is turned off, it cannot capture every turn on. Still, for personal use, it’s a great way to view open events instantly.
Key steps review:
- put up Node.js + Express server.
- produce Unique ID/pixel.
-
log Require
/tracker/:id.png
. -
Serve legal
.png
document. - Embed Pixels in Gmail messages.
- Check Your open event log (and explain it carefully).
That’s it – your own personal email tracking system. Once you see it in action, you’ll gain a deeper understanding of how the major email platforms do open tracking at scale, and why they all suffer from the same caching and image blocking limitations.
GitHub link: Email tracker