Spotting multiple signups from the same company
· 6 min read · Company enrichment
To spot teammates signing up separately, group signups by the registrable domain of their verified email address, keep only company domains (drop webmail, ISPs, disposable, educational and government domains), and look for two or more users from the same domain in different workspaces within a time window. Then act on it: offer the newer user a way to join the existing workspace, or tell the account owner and sales that a team is forming.
This is one of the cheapest growth signals a self-serve product has. The data is already in your users table. What takes care is the filtering, because a naive "same domain" check produces mostly false matches.
Why separate workspaces are worth catching
In a product-led motion, a team often adopts a tool one person at a time. If the second and third people each create their own workspace, you get several free accounts that never hit the collaboration features, billing limits or admin needs that drive a team plan. Merge them early and the team experiences the product the way it was designed. Catch it late and you may be asking people to migrate data between workspaces, which they won't do.
It's also a sales signal. Three people from one 2,000-person company trying your product in the same month is a very different lead from one person at a three-person startup.
Step 1: key on the registrable domain
Group by the registrable domain (eTLD+1), not the full email host. jane@eu.acme.com and raj@acme.com are on the same team for this purpose, and acme.co.uk must not collapse into co.uk. eTLD+1 explained covers the library calls.
Store the normalized domain and its category on the user row at signup. You'll query them constantly, and recomputing from the email every time invites inconsistent normalization.
Step 2: filter down to domains that mean "same organization"
| Category | Same domain means same team? | What to do |
|---|---|---|
company |
Usually | Candidate for matching |
free (gmail.com, outlook.com…) |
No | Never match |
isp (comcast.net, btinternet.com…) |
No | Never match |
disposable |
No | Shouldn't have an account at all |
educational |
Rarely | Match only if you sell to institutions |
government |
Rarely | Same as educational |
Free webmail is the obvious one: there are about 100 provider domains to exclude, and getting even one wrong (say, a regional provider you didn't know about) means you'll suggest that two strangers in Germany join the same workspace. ISP mail domains are the ones people forget. The business vs personal email guide covers both.
Universities deserve their own rule. Two people at stanford.edu are almost never on the same team; they might be in different departments, or one is a student and the other is a professor. If you sell to academic departments, group by full hostname (cs.stanford.edu) rather than the registrable domain, and treat even that as a weak signal. The educational domains category shows how broad these TLD rules are.
pingutil returns the category for an address in milliseconds when you pass skip_enrichment: true, and the domain it returns is already the registrable domain, so one call at signup gives you both values to store.
Step 3: only count verified addresses
Never match on an unverified email. Anyone can type ceo@yourbiggestcustomer.com into a signup form. If that alone grants visibility into another workspace, or even triggers a "your colleague is here" email, you've built an account-takeover helper.
The rule is simple: domain matching starts after the user clicks the verification link (or signs in through SSO with that address).
Step 4: find the clusters
With domain, domain_category, email_verified_at and workspace_id on your users table, the core query is short. Postgres:
-- Company domains with verified users spread across 2+ workspaces, last 30 days
SELECT
u.domain,
COUNT(DISTINCT u.workspace_id) AS workspaces,
COUNT(*) AS users,
MIN(u.created_at) AS first_signup,
ARRAY_AGG(u.workspace_id ORDER BY u.created_at) AS workspace_ids
FROM users u
WHERE u.domain_category = 'company'
AND u.email_verified_at IS NOT NULL
AND u.created_at > now() - interval '30 days'
AND u.domain <> 'yourcompany.com' -- exclude your own staff
GROUP BY u.domain
HAVING COUNT(DISTINCT u.workspace_id) >= 2
ORDER BY workspaces DESC, users DESC;
Run it daily for the sales view. For the in-product prompt, do the same check at signup time for a single domain.
Step 5: decide what to do with a match
At signup, after verification, you can look the domain up and decide which path to take. The company size estimate is useful here because it changes what "same domain" means.
type Match = { workspaceId: string; discoverable: boolean };
export async function onEmailVerified(user: { id: string; email: string }) {
const res = await fetch('https://pingutil.com/v1/lookup', {
method: 'POST',
headers: {
authorization: `Bearer ${process.env.PINGUTIL_KEY}`,
'content-type': 'application/json',
},
body: JSON.stringify({ input: user.email }), // full lookup: we want company.employee_size_estimate
});
const data = await res.json();
await db.users.update(user.id, { domain: data.domain, domain_category: data.category });
if (data.category !== 'company' || data.verdict.is_spam) return;
const matches: Match[] = await db.workspacesWithVerifiedUsersOnDomain(data.domain);
if (matches.length === 0) return;
const size = data.company?.employee_size_estimate ?? null;
const large = size === '1001-5000' || size === '5001-10000' || size === '10000+';
// Small company: very likely the same team. Offer to join a discoverable workspace.
const joinable = matches.filter((m) => m.discoverable);
if (!large && joinable.length > 0) {
await showJoinPrompt(user.id, joinable.map((m) => m.workspaceId));
}
// Large company: probably different teams. Don't merge, tell sales.
if (large || matches.length >= 2) {
await notifySales({ domain: data.domain, company: data.company?.name, workspaces: matches.length + 1 });
}
}
db, showJoinPrompt and notifySales are your own functions. Two things in that code matter more than the details.
Size changes the meaning. In a 30-person company, two signups from the same domain are probably the same team. In a 50,000-person company they probably aren't, and suggesting they share a workspace is wrong and slightly creepy. The employee_size_estimate field comes from LLM enrichment and can be wrong, especially for small companies; how accurate LLM enrichment is explains where. Using it to choose between "prompt to join" and "alert sales" is a low-stakes use. Using it to auto-merge workspaces would not be.
Discoverability is the admin's choice. Slack's model is a good one to copy: workspace owners can approve email domains so that anyone with an address on them can join. The admin opts in. Without that opt-in, don't show the new user the existing workspace's name or members. A generic "Others at your company may already use this. Ask your admin for an invite." is as far as you should go.
Edge cases that will produce false matches
- Consultancies and agencies. Two people from the same agency are often working for different clients. If they keep their workspaces separate after you prompt, let them.
- Companies with several domains.
acme.comandacme.de, or a parent and an acquired brand. Domain grouping won't connect them. The enrichmentparent_companyfield sometimes hints at the link, but it's model output; treat it as a suggestion for a human, not a join key. A small manual alias table works better. - Shared hosting domains. Some providers hand out addresses on their own domain to customers. A domain whose users have nothing else in common is probably one of these; add it to an exclusion list.
- Your own team and test accounts. Exclude your company domain and any QA domains, or your sales channel fills with alerts about yourself.
- Bots on company-looking domains. Signups like
x7k2q9@newly-registered-domain.compass the category filter. Checkingverdict.is_spamand email verification removes most of them. The fake signups guide covers the rest.
What to measure
Track how many domains have two or more workspaces, how many join prompts are shown and accepted, and how many sales alerts become conversations. Don't expect a benchmark from anyone else; the right threshold and window depend on your product and your buyers. Start with "two verified users, separate workspaces, 30 days", watch it for a month, then tighten or loosen.
The short checklist: registrable domain as the key, company category only, verified addresses only, size-aware actions, and admin opt-in before anyone sees anyone else's workspace.
Questions people ask
- Can I automatically add new signups to an existing workspace from the same company domain?
- Only if the workspace admin has opted in and the new user has verified their email address. Without both, you risk putting a stranger into someone else's workspace, and an unverified address can be typed by anyone.
- Is it a privacy problem to tell a new user that colleagues already use the product?
- It can be. Revealing that a named person at their company has an account discloses that person's activity. Safer patterns are letting an admin make their workspace discoverable to verified users on their domain, or showing only a generic prompt to request access.
- How many signups from one domain should trigger a sales alert?
- There is no universal number. Start with two or more verified users in separate workspaces within 30 days, look at the leads it produces for a few weeks, and adjust the threshold and window against your own conversion data.