Your users table is chaos

May 26, 2026



Self-serve means your users table is chaos. When anyone can sign up with any email address, the users table is inevitably polluted with throwaway emails. Self-serve users to orgs is a many-to-many relationship, so this means your orgs table is also chaos: users will create throwaway orgs to test out your product.

Chaos is fine. Ever grep through ps? It’s a mess down there. You mostly care about order for your internal tooling: your CRM, support queue, financial reports: your “human-in-the-loop” tools.

Filtering for paid

It’s worth filtering out free signups, both users and orgs. They are the main source of chaos. You get spammers, abusers, etc. Most PLG companies have a “cheap” tier (ours is $30/month) and opting into the sales funnel or triggering the free trials (with CC) is typically dead simple. Free users should mostly be addressed via passive education: docs, product onboarding, blog post, etc.

Domains

Domain(s) map to org, account, or company. While some domains will exist across multiple orgs, you can usually decide on “primary” org for each domain. We should have domain “tombstones” to mark cases where a domain should not be included in that org. In case where an org has multiple domains, the newest domain should be considered the primary domain. Track this with a unique domain to org id to SaaS id table.

Picking the primary org should have a few heuristics:

  • Verified domains with automatic invitations
  • Paid tier (the higher, the more primary)
  • Recency as a tie breaker

Emails

Email(s) map to user, contact, or people. While emails are enforced as unique in Kernel, users might be part of multiple orgs. Like domain, there is always a primary org for business purposes and mapping to that is sufficient. Track this with a unique email to user id to SaaS id table.

Tombstones

There are always exceptions to every rule. Alerting on cases where a domain or user could be associated with multiple orgs (where both orgs are legit) and creating “tombstones” to disassociate a domain-org or user-org connection. Both domain and email tables should have a “tombstone” array type column, each within a GIN index for fast membership querying.

Consolidation

Domain/email drift can happen in two directions: SaaS added a new contact or Kernel has added a new user. When a new contact is added in the SaaS app, we should attempt to find its matching account. If a new user is added in Kernel, we should attempt to link it to existing SaaS contacts. If we fail to find a link, no-op.

Deletion

If a user membership is removed from an org, recalculate and reassign. If a user is deleted, we simply leave those contacts as unassigned to an account. In Pylon, deleting a contact will delete underlying issues.

Webhooks

We need triggers from our GTM tools, billing, and product (user, org, or membership updates). You probably also want a once-per-minute cron to force updates.

Dry-run

Dry-runs are obviously required for all integration code, but I’m especially interested in how to maintain these integrations over time with agents. I have no interest of manually maintaining my mini-Fivetran. Most of it was vibe-coded with a large spec on the data flows and critical business decisions. Another engineer starts work on it this week, so it’ll be interesting to see how we collaborate on the code.