Qverio is a real-time email verification SaaS. You submit a list of email addresses — or query one via API — and it tells you immediately whether each address is valid, the risk level, and the MX/SMTP details. It's used by email marketers, lead generation teams, and SaaS companies to clean their lists before sending.
This is the unfiltered version of how I built it: the technical decisions, what I got wrong, what I'd do differently, and the specific challenges that turned out to be harder than expected. If you're planning to build a data processing SaaS, there's a lot here that applies directly.
Why I Built It
The email verification market has plenty of players — ZeroBounce, NeverBounce, Hunter.io, Mailfloss. I didn't build Qverio because I thought the market was underserved. I built it because I kept getting asked by clients to integrate email verification into their platforms, and the existing APIs all had friction: complex pricing tiers, slow response times, or rate limits that made real-time single-address lookup frustrating.
I also wanted a project that would teach me something. SMTP verification isn't something most developers have done — it requires understanding email infrastructure at a level that goes well beyond "send an email." I found that genuinely interesting.
The Technical Architecture
The core stack: Laravel 11 for the application layer, MySQL for data storage, Redis for queue management and rate limiting, Alpine.js for the frontend, Stripe for billing, and a DigitalOcean Droplet for hosting.
The verification pipeline has three stages:
1. Syntax & Format Check
Basic regex validation plus RFC 5322 compliance. This is trivial but important to do first — no point running DNS lookups on 'not-an-email'.
2. DNS & MX Record Check
Query the domain's DNS records to verify it exists and has MX records pointing to a mail server. A domain with no MX records cannot receive email. This eliminates a large percentage of invalid addresses quickly and cheaply.
3. SMTP Verification
Connect to the mail server, issue RCPT TO: commands, and observe the response. A 250 response means the mailbox exists. A 550 means it doesn't. This is the expensive part — it requires an actual network connection to the remote server.
The Hardest Part: SMTP Verification at Scale
SMTP verification sounds straightforward but has three major complicating factors:
Catch-all domains. Some mail servers accept RCPT TO for any address at their domain, even if the mailbox doesn't exist. Gmail does not do this; most large corporate domains do. When your server says "yes" to every address, SMTP verification can't tell you if the specific mailbox is real. I mark these as "accept-all" and score them with a confidence rating based on domain age, MX provider, and historical data.
Rate limiting by mail servers. Major providers — Microsoft 365, Google Workspace, Yahoo — aggressively throttle and block SMTP verification requests. If you hammer their servers with too many RCPT TO commands from a single IP, they'll block you. I maintain a pool of outbound IPs and implemented per-domain rate limiting in Redis to stay under detection thresholds.
Timeout handling. Some servers respond in 50ms. Some take 8 seconds. Some never respond at all (greylisting, firewalls). I settled on a 6-second timeout with three retry attempts on different IPs before marking an address as "timeout/unknown." This balances accuracy against queue throughput.
The Queue Architecture
Bulk list verification is where queuing becomes critical. A user uploading 50,000 emails can't wait for synchronous processing — they get a "we're processing your list" confirmation and the work happens in the background.
I use Laravel Horizon with Redis queues. Each verification job is dispatched to one of three priority queues: realtime (single-address API lookups), bulk-high (premium plan users), and bulk-low (free/basic plan users).
The priority queue system means premium users never wait behind a free user who uploaded 100,000 emails. This is a critical design decision for any SaaS that processes variable-size workloads.
Horizontal scaling:
Worker processes run on separate server instances from the web layer. I can spin up additional worker instances during high load without touching the web servers. Laravel Horizon makes this trivial — it's one of the best pieces of Laravel's ecosystem.
Billing with Stripe: Credits vs. Subscriptions
I went with a hybrid model: subscriptions that include a monthly credit allocation, plus pay-as-you-go credits that can be purchased at any time. This is common in API-first products and Stripe handles it, but it took significant setup.
The key architectural decision: I track credits in my own database, not in Stripe. Stripe is the source of truth for billing events (subscriptions created, payments succeeded/failed), but my database is the source of truth for credit balances. This gives me flexibility — I can issue bonus credits, apply discounts, or implement referral rewards without touching Stripe's metering system.
Stripe webhooks update the database on payment events. Every verification job decrements the credit balance atomically (MySQL transactions) to prevent double-spending under concurrent requests.
The Mistakes I Made
Honesty about what went wrong is more useful than a clean success story:
- Under-estimated the catch-all problem: I thought it would be a small edge case. It's not — roughly 15-20% of business email domains are catch-all. My initial result accuracy was misleadingly high because I was calling catch-all domains 'valid.' I had to add a whole scoring system and risk classification layer after launch.
- Built a complex multi-step onboarding before validating demand: I spent three weeks building an elaborate onboarding flow with a guided tour, sample data, and progress tracking. The first five users ignored all of it and went straight to uploading their list. I simplified it down to 'upload a file or paste emails, get results.' Classic over-engineering.
- No rate limiting on my own API at launch: A user wrote a script that hit the API 10,000 times in 20 minutes during the free trial period. It used up significant server resources and caused latency for other users. I added rate limiting immediately, but I should have had it from day one.
- Wrong pricing tier structure: I launched with three tiers: 1,000 / 10,000 / unlimited. The 'unlimited' tier was too expensive for the value it offered, so no one bought it. Most customers landed on the middle tier. I restructured to 5,000 / 25,000 / 100,000 credits and conversion improved significantly.
What I Got Right
- Building the API first: Qverio has a REST API from day one. The dashboard is essentially a UI on top of the same API. This made it easy for technical users to integrate immediately, and it forced clean design of the core functionality.
- Structured result data: Each verification returns a consistent JSON object with status, sub-status, free_email flag, disposable flag, role-based flag, MX provider, and a confidence score. Users know exactly what they're getting and can build downstream logic against the structure.
- Detailed activity log: Every API call is logged with timestamp, email (hashed for privacy), result, processing time, and credit used. Users can audit their usage and I can debug issues without guessing.
What I'd Do Differently
If I started Qverio today, knowing what I know now:
- 1Launch with a much smaller MVP — just single-address verification, no bulk upload, no dashboard. Validate demand before building the full platform.
- 2Spend more time on the catch-all classification system before launch, not after.
- 3Implement usage-based pricing with Stripe Meters from the beginning rather than the custom credit system — it's less flexible but much simpler.
- 4Set up infrastructure monitoring (Uptime Robot, Sentry) before the first user, not after the first outage.
- 5Write integration docs on day one. Developer users will not use your product if they have to figure out the API from the response structure alone.
The Takeaway for SaaS Builders
The technical challenges of building Qverio were significant but solvable. The harder lessons were about product decisions — scope, pricing, onboarding, when to launch. Every week spent building features that users don't use is a week not spent talking to potential customers.
If you're building a SaaS product and want to avoid the mistakes I made, the most valuable thing I can offer is direct experience. I've now built three SaaS products from scratch and helped dozens of founders scope and architect theirs. That's a lot of hard-won patterns to learn from.
Building a SaaS product?
I can take your idea from spec to production — architecture, billing, auth, API design, everything. Tell me about your product and I'll put together a scope estimate.