Building DomainPulse: A Domain Expiry Tracking SaaS
The Problem
Existing domain monitoring tools like ExpiredDomains.net are built for SEO professionals — packed with features that overwhelm anyone who just wants to watch a few domains. I wanted something simple: add a domain, get notified before it expires.
What DomainPulse Does
DomainPulse is a minimalist, mobile-first platform for tracking domain name expiration dates. The core workflow is straightforward:
- Look up any domain to see its WHOIS data instantly
- Add it to your watchlist
- Get email alerts at customizable intervals (90, 60, 30, 14, 7, and 1 day before expiry)
It also includes magic link authentication, Google/GitHub OAuth, and Stripe-powered billing with a free tier (3 domains) and a Pro tier (50 domains).
The Tech Stack
I chose Go for the backend because of its performance characteristics and straightforward concurrency model — ideal for handling WHOIS lookups and background job processing.
Backend
- Go + Fiber — Express-like HTTP framework with excellent performance
- PostgreSQL with sqlc — Type-safe SQL code generation, no ORM overhead
- River — Go-native background job queue backed by Redis for scheduled WHOIS refreshes and alert delivery
- Redis — Caching WHOIS responses and rate limiting
- Resend — Transactional email delivery
Frontend
- Next.js 14 with App Router and TypeScript
- Tailwind CSS — Mobile-first responsive design
- Custom auth context — JWT-based session management
Architecture Decisions
Why Go Over Node.js?
WHOIS lookups are I/O-bound operations that benefit from Go's goroutine model. The backend handles concurrent lookups efficiently without the callback complexity. The likexian/whois library provides pure-Go WHOIS resolution with no external dependencies.
Separation of API and Worker
The backend runs as two separate processes:
- API server (
cmd/api/) — Handles HTTP requests via Fiber - Worker (
cmd/worker/) — Processes background jobs via River (WHOIS refreshes, alert emails)
This separation means the API stays responsive even during heavy background processing.
Type-Safe SQL with sqlc
Instead of an ORM, I used sqlc to generate Go code directly from SQL queries. You write the SQL, sqlc generates type-safe Go functions:
-- name: GetUserDomains :many
SELECT * FROM domains
WHERE user_id = $1
ORDER BY expiry_date ASC;
This gives you full control over queries while maintaining type safety — the best of both worlds.
WHOIS Caching
WHOIS lookups are slow (1-2 seconds) and rate-limited by registrars. DomainPulse uses a shared cache in PostgreSQL (whois_cache table) so multiple users watching the same domain don't trigger redundant lookups.
Domain Status Tracking
Each domain is tracked with a status that maps to visual indicators in the UI:
- Registered — Currently owned, expiry date known
- Expiring Soon — Within the alert window
- Grace Period — Expired but still reclaimable by the owner
- Available — Dropped and available for registration
- Unknown — WHOIS data unavailable
What I Learned
Building DomainPulse reinforced a few things:
- Start with the data model. Getting the PostgreSQL schema right early (users, domains, alerts, whois_cache) made everything downstream simpler.
- Background jobs are essential for SaaS. Anything that touches external APIs (WHOIS, email, Stripe) should be async.
- Mobile-first isn't just CSS. It changes how you think about information hierarchy and interaction patterns.
- Go's standard library is powerful. Between
net/http,encoding/json, and the ecosystem, you need surprisingly few dependencies.
What's Next
The roadmap includes a browser extension for one-click domain watching, bulk CSV import for Pro users, and an MCP server for AI agent integration. Stay tuned.