Two days before I planned to open signups, I ran one last audit of the database. Good thing I did — because the account-creation flow was quietly broken in a way no amount of clicking around the UI would have revealed.
A Postgres trigger meant to copy new signups into my application's user table was wired to the wrong table entirely. Every brand-new signup would have failed outright, and every existing account was silently missing an email address — which meant digest emails were quietly going to nobody. Caught and fixed before signups opened; no real user was ever affected.
Impact
Two things would have broken the moment a real person signed up, and one thing was already broken for every account that existed:
- New signups would have failed outright. Supabase surfaces the error as a generic
Database error saving new user— no useful detail, just a dead end at the registration form. - Every digest email, for every account, forever. The application-side user row for existing accounts was missing an email address. My digest job filters on
WHERE email IS NOT NULL— so it would have skipped every single user, silently, with no error to notice anywhere.
Because I found this during a pre-launch audit, not in production, nobody was actually affected — but had I opened signups as planned, this would have been a silent, total outage of both flows on day one.
Root cause
Impulseye stores users in two places. Supabase manages authentication in its own auth table; my application data — plan tier, monitor limits, email for digests — lives in a separate row I create alongside it. The bridge between them is a Postgres trigger that fires whenever a new auth user is inserted.
The trigger looked reasonable at a glance — which is exactly why it survived so long unnoticed:
-- The broken version
CREATE FUNCTION handle_new_user()
RETURNS trigger AS $$
BEGIN
NEW.plan_tier := 'free';
NEW.monitor_limit := 10;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;This trigger sets NEW.plan_tier and NEW.monitor_limit — but it fires on the auth table, which has neither of those columns. It also never inserts a row into the application's own user table at all.
Here's the insidious part: my own account, and every test account I had, was created before this trigger existed, back when I was seeding rows by hand. So every dashboard I looked at worked perfectly — the bug only affected accounts that didn't exist yet, which is exactly the set of accounts I never re-tested against.
A green dashboard proves the accounts that already exist work. It says nothing about the ones that don't yet.
The fix
The corrected trigger does the one thing it always should have: insert a row into the application's user table, carrying the email across so digests have somewhere to go. Column defaults handle plan tier and limits.
-- The corrected version
CREATE OR REPLACE FUNCTION handle_new_user()
RETURNS trigger
LANGUAGE plpgsql AS $$
BEGIN
INSERT INTO public.profiles (id, email)
VALUES (NEW.id, NEW.email)
ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email;
RETURN NEW;
END;
$$;And because existing accounts already had a missing email from the broken era, one backfill to repair them:
Prevention
The lesson wasn't "write better triggers." It was that my testing only ever exercised state that already existed. So I added one rule to the pre-launch checklist that would have caught this in seconds:
Before any launch, register a brand-new account with a fresh email and confirm the full downstream state: the application row exists, its email is set, the plan tier and limits are correct, and a test digest actually sends. Test the path a stranger takes, not the one your own account already took.
It's a boring check. It's also the exact check that separates a smooth launch day from a morning of confused "I can't sign up" emails — the kind of emails that, ironically, my own digest system wouldn't have been able to send.