How It Works#
The mechanism is simple, but the details matter for accuracy and compliance.
sequenceDiagram
participant S as Sender (Go server)
participant MTA as Mail Transfer Agent
participant C as Email Client
participant T as Tracking Server
S->>S: Generate UUID for recipient
S->>S: Build HTML email with img src=tracking URL
S->>MTA: Send email via SMTP
MTA->>C: Deliver email to inbox
C->>C: User opens email
C->>T: GET /pixel/{uuid}.png
T->>T: Look up UUID, record open event
T->>C: 200 OK with 1x1 transparent PNG
The tracking server must serve the pixel in every case, including when the UUID is not recognized. Returning a 404 causes some email clients to retry indefinitely, inflating your open counts and hammering your server.
The Naive Implementation (and Its Flaws)#
The original version of this post had a critical syntax error: http.ListenAndServe was placed inside the handler function’s closure body, which means it only runs when a request arrives – and the handler never returns, hanging indefinitely on the first request. Here is the corrected minimal version first:
package main
import (
"log"
"net/http"
"time"
)
func main() {
http.HandleFunc("/pixel.png", pixelHandler)
// ListenAndServe must be at the top level of main, not inside a handler.
log.Fatal(http.ListenAndServe(":8080", nil))
}
func pixelHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("open event from %s user-agent=%s", r.RemoteAddr, r.UserAgent())
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Expires", time.Now().UTC().Format(http.TimeFormat))
// Serve a 1x1 transparent PNG (base64-decoded inline to avoid filesystem dependency).
w.Write(transparentPNG)
}
// transparentPNG is the smallest valid 1x1 transparent PNG (67 bytes).
var transparentPNG = []byte{
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4,
0x89, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41,
0x54, 0x78, 0x9c, 0x62, 0x00, 0x01, 0x00, 0x00,
0x05, 0x00, 0x01, 0x0d, 0x0a, 0x2d, 0xb4, 0x00,
0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
0x42, 0x60, 0x82,
}This works, but it is useless for production: every request looks identical. You cannot tell which recipient opened the email, how many unique opens there were, or whether a single user opened it multiple times.
Unique Per-Recipient URLs#
The entire value of tracking pixels comes from giving each recipient a unique URL. The server maps the URL’s UUID back to the recipient at open time.
package tracker
import (
"sync"
"time"
)
// TrackingRecord stores metadata about a single email send.
type TrackingRecord struct {
RecipientEmail string
Subject string
SentAt time.Time
FirstOpenAt *time.Time
OpenCount int
}
// Store is an in-memory open tracking store.
// In production, replace with a database (PostgreSQL, Redis, DynamoDB).
type Store struct {
mu sync.RWMutex
records map[string]*TrackingRecord // key: UUID
}
func NewStore() *Store {
return &Store{records: make(map[string]*TrackingRecord)}
}
// Register creates a new tracking record and returns its UUID.
func (s *Store) Register(uuid, recipient, subject string) {
s.mu.Lock()
defer s.mu.Unlock()
s.records[uuid] = &TrackingRecord{
RecipientEmail: recipient,
Subject: subject,
SentAt: time.Now(),
}
}
// RecordOpen marks the tracking record as opened. Thread-safe.
// Returns the record, or nil if the UUID is unknown.
func (s *Store) RecordOpen(uuid string) *TrackingRecord {
s.mu.Lock()
defer s.mu.Unlock()
rec, ok := s.records[uuid]
if !ok {
return nil
}
now := time.Now()
if rec.FirstOpenAt == nil {
rec.FirstOpenAt = &now
}
rec.OpenCount++
return rec
}Complete Tracking Server#
package tracker
import (
"log/slog"
"net/http"
"strings"
"time"
)
type Server struct {
store *Store
mux *http.ServeMux
}
func NewServer(store *Store) *Server {
s := &Server{store: store, mux: http.NewServeMux()}
s.mux.HandleFunc("/pixel/", s.handlePixel)
return s
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.mux.ServeHTTP(w, r)
}
// handlePixel handles GET /pixel/{uuid}.png
func (s *Server) handlePixel(w http.ResponseWriter, r *http.Request) {
// Extract UUID from path: /pixel/550e8400-e29b-41d4-a716-446655440000.png
path := strings.TrimPrefix(r.URL.Path, "/pixel/")
uuid := strings.TrimSuffix(path, ".png")
rec := s.store.RecordOpen(uuid)
if rec != nil {
slog.Info("email opened",
"uuid", uuid,
"recipient", rec.RecipientEmail,
"subject", rec.Subject,
"open_count", rec.OpenCount,
"user_agent", r.UserAgent(),
"remote_addr", r.RemoteAddr,
)
} else {
slog.Warn("unknown tracking UUID", "uuid", uuid)
}
// Always serve the pixel -- even for unknown UUIDs.
// Returning 404 causes retry storms from aggressive email clients.
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", time.Now().UTC().Format(http.TimeFormat))
w.WriteHeader(http.StatusOK)
w.Write(transparentPNG)
}Generating a UUID per send and registering the tracking record:
package mailer
import (
"crypto/rand"
"fmt"
"tracker"
)
func newUUID() string {
b := make([]byte, 16)
rand.Read(b)
return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x",
b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}
func SendTrackedEmail(store *tracker.Store, recipient, subject, baseURL string) {
uuid := newUUID()
store.Register(uuid, recipient, subject)
pixelURL := fmt.Sprintf("%s/pixel/%s.png", baseURL, uuid)
html := buildEmailHTML(subject, pixelURL)
// Send html via your SMTP library here.
_ = html
}HTML Email Template with Tracking Pixel#
The pixel must be the last element inside the <body> to maximize the chance that it loads even when the user scrolls to the bottom and the email client loads images progressively.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{.Subject}}</title>
</head>
<body>
<p>Hello {{.RecipientName}},</p>
<p>{{.Body}}</p>
<p>Best regards,<br>The Team</p>
<!--
Tracking pixel: 1x1 transparent PNG.
width/height=1 prevents layout shift.
alt="" prevents screen readers from announcing it.
Placed at the end of body to avoid blocking render.
-->
<img src="{{.PixelURL}}"
width="1" height="1"
alt=""
style="display:block;width:1px;height:1px;border:0;" />
</body>
</html>Never use a CSS display:none trick to hide the pixel. Several email clients do not load hidden images at all, defeating the entire purpose. Use width="1" height="1" with an explicit style instead.
Email Client Reality: Image Blocking#
Tracking pixels are not reliable open indicators. Most major email clients block remote image loading by default.
| Client | Default image loading | Notes |
|---|---|---|
| Gmail (web) | Blocked, proxied via Google | Opens are proxied; IP is Google’s data center, not recipient |
| Outlook (desktop) | Blocked by default | User must click “Download pictures” |
| Apple Mail (iOS/macOS 15+) | Pre-fetched by Apple | Apple Mail Privacy Protection fetches images on behalf of the user, even if they never open the email |
| Outlook.com (web) | Blocked by default | Renders through Microsoft proxy when allowed |
| Thunderbird | Blocked by default | User-controlled per-message |
The practical implication: tracking pixels capture roughly 40-60% of actual opens in a typical B2B email list. Apple Mail Privacy Protection (AMPP), introduced in iOS 15 and macOS Monterey, actively breaks open tracking for Apple users by pre-loading all remote images immediately on delivery. This inflates open rates for Apple Mail users while making them meaningless as engagement signals.
Do not use open rates as a primary metric for email campaign decisions. Use click-through rates (UTM-tagged links) instead – those are unaffected by image blocking and provide a much stronger signal of intent.
GDPR Compliance#
Tracking pixels collect personal data: the timestamp of an open event linked to an email address, plus the IP address and user agent of the device that loaded the image. Under GDPR Article 4, this is personal data. Processing it requires a lawful basis.
Applicable for: transactional emails (receipts, delivery confirmations, password resets) where tracking whether the email was received is a legitimate operational need.
Requirements: document the legitimate interest assessment (LIA), ensure tracking is proportionate (not used for profiling), provide clear disclosure in your privacy policy, and honor opt-out requests.
Not applicable for: marketing emails where the primary purpose is measuring campaign engagement.
Applicable for: marketing emails and any situation where legitimate interest does not apply.
Requirements: obtain explicit, informed consent before sending tracked emails. The consent must be specific to tracking (not bundled with general terms of service acceptance). Maintain an audit trail of consent.
Practical implementation: include a preference center where subscribers can opt out of tracking while still receiving emails.
Minimum privacy policy disclosure:
We use tracking pixels in our emails to measure whether emails are opened and to collect the date, time, and device type of the open event. This data is used to [specific purpose]. You can opt out of email tracking by [specific mechanism – e.g., disabling image loading in your email client, or updating your preferences at this link].
Production Considerations#
CDN for the pixel server. If you send millions of emails and 40% of recipients open them within the first hour, your pixel server will receive a spike of hundreds of thousands of requests within minutes of send time. Put the pixel server behind a CDN (CloudFront, Fastly) or a load balancer with horizontal scaling. The handler itself is stateless read from a database, which is easy to scale.
Database instead of in-memory store. The Store implementation above loses all data on restart. In production, use a database. PostgreSQL with a simple email_opens table and a uuid primary key is sufficient. For very high volume, Redis with INCR and SETNX provides sub-millisecond write latency.
UUID collision probability. A UUID v4 is 122 bits of entropy. The probability of collision in 10^12 (one trillion) generated UUIDs is approximately 10^-13. For email tracking at any realistic scale, collisions are not a practical concern. Use crypto/rand (as shown above), not math/rand.
Deduplication for multiple opens. A single user opening an email five times generates five pixel requests. Your analytics should distinguish “unique opens” (first open per UUID) from “total opens” (sum of all open events per UUID). The TrackingRecord struct above tracks both via FirstOpenAt and OpenCount.
Bot traffic. Email security gateways (Proofpoint, Mimecast, Barracuda) scan email links and images before delivery to check for malware. This pre-scanning triggers your tracking pixel before any human sees the email. Identify bot opens by checking whether the user agent matches known scanner patterns and filtering them from your analytics.
If you want to go deeper on any of this, I offer 1:1 coaching sessions for engineers working on AI integration, cloud architecture, and platform engineering. Book a session (50 EUR / 60 min) or reach out at manuel.fedele+website@gmail.com.