← back to writing
Engineering deep dive

DataSync: I built the wrong product first, on purpose

I started DataSync as an alumni portal. It's a data-freshness SaaS now, and getting from one to the other is the actual story.

The problem

Every alumni network, CRM, or HR directory has the same quiet failure: data was correct once, then nobody updated it. Six months later 40% of it is stale, and nobody notices until a mailer bounces or an emergency contact isn't real anymore. "Please verify your details" reminder emails don't fix this — nobody opens them. So I built a real instance of the problem first, with a real audience, instead of guessing at the abstract version.

What's actually there

Next.js 14, Supabase for auth and Postgres, deployed on Vercel — nothing exotic, the point was never the stack. On sign-up, a server route creates a blank profiles row so there's never a user without one. Every profile save stamps last_verified with the current timestamp — not "did they fill this in," but "how long ago did a human confirm it's still true."

Next to Save sits a second button: Data is same. One click stamps last_verified without touching a single field. That's the part that makes reminders work — it turns "update your profile" (a form, friction) into "still true, yes/no" (one tap). People do the second one.

The reminder job

A GitHub Actions workflow runs every Sunday and calls send_reminders.js, which queries Supabase directly with the service role key for any profile unverified in six months — and skips anyone reminded within the last six, so nobody gets nagged weekly. It sends through SendGrid and stamps last_reminder_sent on every send.

It's had a --dry flag since day one: print who would get emailed, send nothing. I did not want to find out the query was wrong by mailing three hundred alumni at 9am on a Sunday.

Where it's going

The current app is single-tenant. The rewrite is multi-tenant: an org signs up, gets an API key, drops a script tag on their own site.

<script src="https://mysaas.com/sdk.js"></script>
<script>
  DataSync.init({ apiKey: 'your_api_key', containerId: 'data-portal' });
</script>
<div id="data-portal"></div>

That needed the alumni portal to exist first — I know last_verified and the one-click confirm matter because I watched them matter on real people, not because I guessed. Alumni networks, healthcare intake, HR directories, membership associations — different shapes of the same failure. Build one instance properly, then find out what has to be configurable.