ImpulseyeImpulseye
FeaturesIntegrationsPricingBlogChangelogIs It Down?
All posts
Postmortem

The signup bug that almost shipped on launch day

A single trigger wired to the wrong table meant every new signup would have failed — and every digest email would have silently gone nowhere. Here's how I found it before opening the doors.

July 2, 2026 6 min read

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.

TL;DR

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:

sql
-- 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;
Spot the bug

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.
- A note I left myself in the audit

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.

sql
-- 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:

The rule

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.

Back to all posts
Share

Monitor your sites with Impulseye

Uptime checks, SSL warnings, and alerts across 9 channels.

Start free

Keep reading

Engineering 5 min

I built myself a tiny ops dashboard. It found 30 real problems in five minutes.

A personal, owner-only page to stop bouncing between the Hetzner, Supabase, and Resend consoles by hand — and it immediately surfaced a stack of real database performance issues I didn't know I had.

July 19, 2026Read
Infrastructure 7 min

My load test was lying to me, and I almost believed it

Before renting a real server, I built a load-testing rig to find Impulseye's actual ceiling. It reported a ceiling that didn't exist — the test harness itself had two bugs, and both of them looked exactly like a capacity problem.

July 14, 2026Read
Engineering 9 min

What seven webhook providers taught me about shipping alerts

Discord, Slack, Teams, Telegram, Google Chat, PagerDuty, and Pushover each format an alert differently — and each one will silently reject you if you don't respect its limits. Here's what I wish I'd known on day one.

June 28, 2026Read
ImpulseyeImpulseye

Uptime monitoring that is dead simple. Instant alerts via email, Slack, and Discord, set up in under 30 seconds.

support@impulseye.com

Product

  • Features
  • Integrations
  • Pricing
  • Blog
  • Changelog

Tools

  • Is It Down?
  • Dashboard
  • Status Pages
  • Impulseye Status
  • API Docs
  • FAQ

Legal

  • Terms of Service
  • Privacy Policy
  • Refund Policy

© 2026 Impulseye. All rights reserved.

ContactTermsPrivacy