Saturday, 22 August 2026

Four Months Building a Domain Research Platform: What I Learned



Earlier this year I set out to build a SaaS platform for domain name research — a tool that helps investors find newly registered domains and expiring auctions worth buying, backed by real business signals instead of gut feeling. Four months and a hundred-plus commits later, it's live. Here's what the journey actually looked like.

What the platform does

The core idea: a domain name is more valuable if real companies are already using that name. So the platform cross-references millions of domain candidates against company datasets — LinkedIn company data, Crunchbase, funding records — and surfaces metrics like "how many companies use this keyword" and "does the .com resolve to a funded startup." Users can filter new registrations and live auctions by dozens of these signals, save filter sets, and even schedule automatic bids on auctions.

Tech stack: React 19 + Vite on the frontend (deployed to Azure Static Web Apps), ASP.NET Core 8 API on the backend, SQL Server for the data.

Lesson 1: At 13 million rows, every query is a performance problem

My biggest recurring theme was SQL performance. The candidates table holds over 13 million rows, and innocent-looking filters would take tens of seconds. Some things that saved me:Denormalize aggressively. Joining a 13M-row table against per-keyword metrics on every page load was never going to work. Copying the hot metric columns onto the candidate rows (updated in batch) turned multi-second queries into instant ones.
Filtered indexes have sharp edges. I had a filtered index that looked perfect but the optimizer couldn't use it for a scan pattern in a sync procedure — that one anti-join was reading 13 GB off the clustered index. Adding a plain non-filtered index cut reads by 10x.
Cap your counts. "Showing 1 of 4,381,022 results" is a nice flex, but computing that exact count on every filter change is expensive. Capping the count query made the filter UI feel instant.

Lesson 2: Third-party data is always staler than you think

The auction feature schedules bids near an auction's end time. Sounds simple — until a user's bid gets rejected with "auction already ended" on an auction that's clearly still live. The cause: the end times I'd cached from the auction provider had drifted. Auctions get extended, removed, and rescheduled constantly.

The fix was layered: re-check the live end time from the provider's API at the moment a bid is scheduled, run a recurring background job to refresh end times for anything with a pending bid, and track an explicit auction status so removed auctions don't masquerade as active ones. The lesson generalizes: any cached copy of someone else's real-time data needs a freshness strategy, not just a sync job.

Related sub-lesson: store everything in UTC, convert at the edges, and let users pick an IANA timezone (falling back to the browser's). I've yet to regret a UTC decision; I've regretted every alternative.

Lesson 3: Background jobs beat polling

Auto-bidding started as scheduled tasks checking "is it time to bid yet?" — wasteful and imprecise. Moving to Hangfire with event-driven, per-auction scheduled jobs made bids fire at exactly the right moment and made the whole thing observable: each auction stores its job ID, so I can see, cancel, or reschedule any pending bid.

Lesson 4: The cloud will hurt you in ways the docs don't mention

Two incidents worth sharing:Azure Static Web Apps just... stopped accepting deployments. Server-side rejections with no useful error, for days. I ended up building a manual deploy recipe as a workaround. Sometimes the fix is a runbook, not a root cause.
ASP.NET Core's DataProtection key ring silently regenerated on App Service Linux, which meant every encrypted secret in the database (users' API credentials for the bidding integrations) became undecryptable overnight. The default key storage is ephemeral in containers. Persisting keys to the database via PersistKeysToDbContext fixed it — but only after users had to re-enter credentials. If you encrypt anything with DataProtection, decide where the keys live before production.

Lesson 5: Multi-tenancy is a hundred small decisions

Turning an internal tool into a licensable product meant feature entitlements: internal users see everything, external users see what their plan includes, and premium data sources get gated field-by-field in API responses — not just hidden in the UI. Doing this properly took eight phases of work and touched nearly every endpoint. My advice: design the entitlement model early, even if you launch with one plan. Retrofitting gating onto finished endpoints is slow, careful work.

What's next

Subscription billing with Stripe, and a data pipeline to generate my own TLD zone statistics from ICANN zone files instead of depending on third-party counts. But that's the next post.

Friday, 18 March 2022

Implemented a Service to receive real time message from an API Webhook

 What is the webhook?  Ask Google :) 😅 

This is my experience to build a Azure Function HTTP trigger to receive an event from SkyBox API Webhook. 

Business requirement: Real time integration INVOICE with SkyBox API Webhook. Means whenever SkyBox has a new or Update invoice. Their service would send the json message to my API.


Solution: 

Step 1: Built C# HTTP trigger function and then deploy it to Azure Function


Depends on the business requirement, you can add more in your own code.

 Step 2: Added a subscription sending a POST to https://skybox.vividseats.com/services/webhooks

{

 "topic""INVOICE",
 "url""https://xxxxxxxxinvoice.azurewebsites.net/api/SkyboxReceiveInvoice?code=xxxxxxxxxx",
 "headers""Bearer: xxxxxxxx",
 "secret""yoursecretkey"
}

Navigate to Azure Function Monitor to see the message coming.

Note:

The "secret": "yoursecretkey" is using for Encrypted the message. Here is code:

+ Create Key:
byte[] key = Encoding.ASCII.GetBytes("TicketShine");

+ Encode to create a check MAC.

public static string Encode(string input, byte[] key)
{
HMACSHA1 myhmacsha1 = new HMACSHA1(key);
byte[] byteArray = Encoding.ASCII.GetBytes(input);
MemoryStream stream = new MemoryStream(byteArray);
return myhmacsha1.ComputeHash(stream).Aggregate("", (s, e) => s + String.Format("{0:x2}", e), s => s);
}

Saturday, 19 February 2022

Gitlab - CICD to Azure App Service with an application .NET Framework

Every development team has unique requirements that can make implementing an efficient deployment pipeline difficult on any cloud service. This is a process to prevents downtime: 


Here is CICD script:


variables:
MSBUILD: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\MSBuild\\Current\\Bin\\msbuild.exe"
SOLUTION: IProcessorSolution.sln
WEBJOB_PROJECT: ".\\IProcessorPdfParser\\IProcessorPdfParser.csproj"
DEV_PUBLISH_PROFILE: "IProcessorWebsiteStaging - Web Deploy"
DEV_PUBLISH_PROFILE_WEBJOB_PROJECT: "IProcessorWebsiteStaging - Web Deploy.pubxml"
DEV_DR_PUBLISH_PROFILE: "iprocessorwebsitestaging-dr - Web Deploy"
DEV_DR_PUBLISH_PROFILE_WEBJOB_PROJECT: "iprocessorwebsitestaging-dr - Web Deploy.pubxml"
PROD_DR_PUBLISH_PROFILE: "iprocessorwebsite-dr - Web Deploy"
PROD_DR_PUBLISH_PROFILE_WEBJOB_PROJECT: "iprocessorwebsite-dr - Web Deploy.pubxml"

stages:
- build
- deploy



deploy_dev_dr:
stage: deploy
environment:
name: staging
rules:
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH != "dev" && $CI_COMMIT_BRANCH != "master"
tags:
- shared-windows
- windows
- windows-1809
script:
- 'nuget restore $SOLUTION'
- '& $MSBUILD $SOLUTION /p:Configuration=Release /p:SignManifests=False /p:DeployOnBuild=true /p:PublishProfile=$DEV_DR_PUBLISH_PROFILE'
- '& $MSBUILD $WEBJOB_PROJECT /p:Configuration=Release /p:SignManifests=False /p:DeployOnBuild=true /p:PublishProfile=$DEV_DR_PUBLISH_PROFILE_WEBJOB_PROJECT'
artifacts:
expire_in: 1 week

deploy_dev:
stage: deploy
environment:
name: staging
rules:
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "dev"
tags:
- shared-windows
- windows
- windows-1809
script:
- 'nuget restore $SOLUTION'
- '& $MSBUILD $SOLUTION /p:Configuration=Release /p:SignManifests=False /p:DeployOnBuild=true /p:PublishProfile=$DEV_PUBLISH_PROFILE'
- '& $MSBUILD $WEBJOB_PROJECT /p:Configuration=Release /p:SignManifests=False /p:DeployOnBuild=true /p:PublishProfile=$DEV_PUBLISH_PROFILE_WEBJOB_PROJECT'
artifacts:
expire_in: 1 week


deploy_prod:
stage: deploy
environment:
name: production
rules:
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "master"
tags:
- shared-windows
- windows
- windows-1809
script:
- 'nuget restore $SOLUTION'
- '& $MSBUILD $SOLUTION /p:Configuration=Release /p:SignManifests=False /p:DeployOnBuild=true /p:PublishProfile=$PROD_DR_PUBLISH_PROFILE'
- '& $MSBUILD $WEBJOB_PROJECT /p:Configuration=Release /p:SignManifests=False /p:DeployOnBuild=true /p:PublishProfile=$PROD_DR_PUBLISH_PROFILE_WEBJOB_PROJECT'
artifacts:
expire_in: 1 week

How to setup P2S VPN to connect Azure SQL

After successful building the VPN connection to Azure Environment by applying P2S VPN. This is a note what I did so that I can use it in the future.



 

Script Powersell to create SelfRoot & Client

$cert = New-SelfSignedCertificate -Type Custom -KeySpec Signature -Subject "CN=VNETROOT" -KeyExportPolicy Exportable -HashAlgorithm sha256 -KeyLength 2048 -CertStoreLocation "Cert:\CurrentUser\My" -KeyUsageProperty Sign -KeyUsage CertSign

New-SelfSignedCertificate -Type Custom -DnsName TANLECLIENT -KeySpec Signature -Subject "CN=VNETCLIENT" -KeyExportPolicy Exportable -HashAlgorithm sha256 -KeyLength 2048 -CertStoreLocation "Cert:\CurrentUser\My" -Signer $cert -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2")

 

Generate and export certificates for P2S: PowerShell - Azure VPN Gateway | Microsoft Docs


 


Step-by-Step guide to Azure Point-to-Site VPN - Technical Blog | REBELADMIN

 

Tutorial: Connect to an Azure SQL server using an Azure Private Endpoint - Portal | Microsoft Docs

 




 

Do the following:

1.      Navigate to "Firewalls and virtual networks" of you SQL server and make sure to set "Deny public network access" to yes.

2.      Create an Azure private endpoint. It will create endpoint for SQL server within your virtual network and it'll be assigned a private IP from within subnet's IP range. You use this private IP to connect to SQL server.

3.      On you local machine, make sure you're connected to VPN and open SQL Server Management Studio:

·         Under "Server name" enter private IP address of Azure private endpoint created in step #2.

·         Login part can be a bit tricky. Under "Login" field, enter username in format "username@public_sql_server_name" (e.g. admin@my-sql-server.database.windows.net). For password, just enter you password.

·         Last thing to do is to click on "Options" and navigate to "Connection properties". Make sure to check "Encrypt connection" and "Trust server certificate". This is required as server's certificate is issued to "my-sql-server.database.windows.net" and you're accessing it via private IP. If this wasn't checked, management studio wouldn't trust server's certificate and would refuse connection.







References:

Connect to a VNet using P2S VPN & certificate authentication: portal - Azure VPN Gateway | Microsoft Docs



Four Months Building a Domain Research Platform: What I Learned

Earlier this year I set out to build a SaaS platform for domain name research — a tool that helps investors find newly registered domains an...