Before we dive in: I share practical insights like this weekly. Join developers and founders getting my newsletter with real solutions to engineering and business challenges.
I've been reaching out to potential clients for my consultancy work, and I kept running into the same frustration: I had no idea if anyone was actually reading my emails. Were they getting lost in spam? Was my messaging unclear? Wrong audience entirely?
I'd used Superhuman years ago, and more recently Shortwave, but the £20-30/month price point never really sat well with me for what's essentially just tracking pixels. I enjoyed the email clients themselves, and understand this is just a feature, but to add tracking I would need to jump up several pricing tiers. I wanted something simpler.
So I built Opened.so. Two days of work, and I had a working SMTP relay that injects tracking pixels transparently. Works with any email client - Apple Mail, Outlook, Thunderbird, whatever you use. No extensions, no switching clients, just change your SMTP server and you're done.
The Core Insight: SMTP is Underrated
The moment I had the idea, I knew SMTP was the answer. Think about it: every email client, on every platform, already knows how to send email through an SMTP server. If I could build an SMTP relay that sits between your email client and the actual delivery service (AWS SES in my case), I could intercept outgoing emails, inject tracking pixels, and forward them on. Completely transparent to the user.
No browser extensions that break on mobile. No forcing people to use a specific email client. Just change one setting in your email configuration and you're tracking emails.
The beauty of this approach is its simplicity. Email clients have been talking SMTP for decades - it's a solved problem. I'm just leveraging that existing infrastructure rather than fighting against it.
The Multi-Recipient Challenge
The interesting technical problem came immediately: how do you track individual recipients when someone sends an email to multiple people?
If I send an email to Alice, Bob, and Charlie, I want to know:
- Did Alice open it?
- Did Bob open it?
- Did Charlie open it?
But here's the catch: email clients expect recipients to see the normal To/Cc/Bcc headers. If Alice gets an email, she should see "To: alice@company.com, bob@company.com, charlie@company.com" - that's just how email works.
The solution is conceptually simple but felt clever when I found the answer: send one physical email per recipient, each with a unique tracking pixel, but preserve all the original headers.
So for that email to three people, my SMTP relay actually sends three separate emails through AWS SES:
- One to Alice with pixel UUID
abc-123
- One to Bob with pixel UUID
def-456
- One to Charlie with pixel UUID
ghi-789
Each recipient sees the exact same To/Cc/Bcc list, email threading works perfectly because the Message-ID is preserved, but I can track each person individually because each email has a different tracking pixel.
The code is straightforward - iterate through all recipients, generate a unique pixel UUID for each, inject that into the HTML body, and send:
for _, recipient := range allRecipients {
pixelUUID := uuid.New()
bodyWithTracking := email.InjectPixel(parsed.HTMLBody, pixelUUID, s.server.pixelDomain)
rawEmail := email.BuildRawEmail(parsed, recipient.Address, bodyWithTracking)
sesMessageID, err := s.server.sesClient.SendRawEmail(ctx, s.from, []string{recipient.Address}, rawEmail)
recipientRecord := &models.Recipient{
EmailID: emailRecord.ID,
ToAddress: recipient.Address,
PixelUUID: pixelUUID,
SESMessageID: &sesMessageID,
}
s.server.db.CreateRecipient(recipientRecord)
}
I planned this part out before starting to code, which saved me from having to refactor later. Understanding the email headers and how threading works was key here.
Link Tracking Without Breaking Things
I also wanted to track link clicks, but this is where things get tricky. You can't just blindly rewrite every URL in an email - you'll break important stuff.
Unsubscribe links, for instance. Email clients look for specific patterns in these links, and if you rewrite them, you risk breaking the unsubscribe functionality. Mailto links shouldn't be rewritten either. Same with anchor links (same-page navigation).
The approach I took is conservative: use a regex to find all <a href="...">
tags, skip anything that's obviously special-purpose, and only track regular HTTP/HTTPS links:
for _, match := range linkRegex.FindAllStringSubmatch(htmlBody, -1) {
originalURL := match[1]
// Skip special links
if strings.HasPrefix(originalURL, "mailto:") ||
strings.HasPrefix(originalURL, "#") ||
strings.Contains(originalURL, "/px/") {
continue
}
linkUUID := uuid.New()
trackingURL := fmt.Sprintf("https://%s/l/%s", trackingDomain, linkUUID)
modifiedHTML = strings.Replace(modifiedHTML, originalURL, trackingURL, -1)
}
When someone clicks a tracked link, my pixel server gets the request, logs the click asynchronously, and immediately redirects to the original URL. The overhead is typically under 10 milliseconds.
Privacy Protection is Now Standard
Here's the reality of email tracking in 2024: it doesn't work perfectly. Apple Mail Privacy Protection and Gmail's image proxy both prefetch emails immediately after delivery, creating fake "opens" that happen within seconds of sending.
I could have tried to hide these prefetch opens entirely, but that felt dishonest. Instead, I detect them and show them to users with context:
func (s *Server) isLikelyPrefetch(userAgent string, sentAt time.Time) bool {
if strings.Contains(userAgent, "PreviewToken") {
return true
}
if strings.Contains(userAgent, "Google-Image-Proxy") {
return true
}
if time.Since(sentAt) < 1*time.Second {
return true
}
return false
}
In the dashboard, opens flagged as "likely privacy protection" show up with a warning. Users see something like:
✓ Opened 2 seconds after send (likely privacy protection)
Device: Apple Mail Privacy Proxy
Location: Hidden by privacy protection
I'm upfront about this: email tracking accuracy is probably 70-80% now due to privacy protection. At £5/month instead of £30/month, I think that's a fair trade-off. If you need perfect tracking, you probably need to rethink your approach entirely.
The Stack: Boring Technology Works
The entire backend is Go, which shouldn't surprise anyone who's read my other articles. For this kind of project - an SMTP relay that needs to handle concurrent connections efficiently - Go is perfect. The emersion/go-smtp
library handles all the SMTP protocol details, so I could focus on the business logic.
I'm using PostgreSQL for storage because I've been burnt by SQLite's locking contention issues before. For a service that's handling multiple concurrent SMTP connections, proper concurrent database access matters. The schema is straightforward: emails, recipients, opens, tracked links, link clicks.
The tracking pixel itself is a 1×1 transparent GIF hardcoded in memory - 43 bytes. No disk I/O, no external requests, just serve the bytes and log the open. Response time is typically 2-5ms.
Frontend is Vue 3 with the Composition API. I prefer Vue over React for personal projects - it's less verbose and the reactivity system just makes sense to me. The dashboard shows your sent emails, per-recipient tracking, opens with location and device detection, and click tracking.
Deployment is a single VPS with Ansible for automation. The entire infrastructure is defined in code, which makes deployments reproducible and rollbacks trivial when something breaks.
Performance and Scale
The numbers so far:
- SMTP relay handles ~100 emails/second on a single instance
- Pixel server can do 10,000 requests/second
- Dashboard loads in ~150ms with 1,000 tracked emails
- Database is tiny - about 10MB per 1,000 emails
The current setup should handle around 1,000 users each sending 50 emails per day without breaking a sweat. If I need to scale beyond that, the architecture supports horizontal scaling - just add more SMTP and pixel servers behind a load balancer.
I'm using PostgreSQL with standard connection pooling. Nothing fancy, but I did learn from my previous work on caching improvements that sometimes the simple approach is the right one.
What I'd Do Differently
Two days from idea to working prototype is pretty good, but there are a few things I'd change if I were starting fresh:
First, I should have started with a shared AWS SES account rather than letting users bring their own. The DNS configuration (SPF, DKIM, DMARC) is intimidating for non-technical users, even with step-by-step guides. A shared sending infrastructure would have simplified onboarding significantly. This does come at some cost, so I am not sure it is the correct approach.
Second, more granular analytics should have been part of the initial plan. Users want to know things like "best time to send" and "which subject lines work better" - this requires storing more metadata from the start.
The Economics of Indie SaaS
The unit economics are interesting. For a user on the £5/month Pro tier sending 2,000 emails:
- AWS SES costs: £0.20
- Server costs: £0.15
- Database costs: £0.12
- Stripe fees: £0.35
- Total: £0.82
That's an 84% gross margin. Break-even is around 4 paying users. This is the future of indie SaaS - simple, focused products that do one thing well and cost a fraction of the incumbents.
Email tracking doesn't need to cost £30/month. It's pixels and database records. The only reason it does is because people are willing to pay it, not because the infrastructure costs justify it.
What's Next
Right now Opened.so is live and I'm the primary user. It's working exactly as I needed - I can see which of my consultancy emails get opened, which links get clicked, and adjust my approach accordingly. The feedback loop is fast and the insights are useful.
I've had a few people express interest in using it, which is encouraging. Once I integrate Stripe properly (payments are stubbed out but not fully wired up), I'll do a proper launch. The product is ready; it's just the billing infrastructure that needs finishing.
If you're interested in email tracking without paying Superhuman prices, or you're just curious about the technical implementation, feel free to sign up. The entire thing was a surprisingly fun weekend project that solved a real problem I had.
Sometimes the best products come from just scratching your own itch and then realising other people have the same itch.
Tech stack: Go 1.21+, PostgreSQL, Vue 3, AWS SES, deployed on a single VPS with Ansible
Related: Simple CI/CD deployment from GitHub to Kubernetes, Mocking Redis and Kafka in Go, Go time handling from basics to production patterns