Short answer: trace suspected duplicate accounts as an evidence graph, not as an email join: normalize only what the email standard safely permits, connect login events through several independently useful signals, and let confidence determine whether the marketplace silently links records, asks for step-up authentication, or leaves them separate. Session security improves when the system can act on strong evidence; user friction stays bounded when a shared device or a recycled address cannot trigger a merge by itself.
That distinction matters in a marketplace because the same browser may serve a household, a managed phone may rotate identifiers, and a seller may legitimately operate separate buyer and business profiles. An exact email lookup catches the easy case. It says very little about the hard one: two account IDs, several sessions, one device fingerprint, and an address whose ownership may have changed.
No single signal gets veto power.
How should identity resolution trace duplicate accounts after an email lookup?
Start with events, not a mutable users row. The useful record is a time-bounded observation: account A authenticated at a particular instant, supplied a verified address, and presented a device signal already reduced to the minimum fields the risk decision needs. Preserve the source and timestamp for each edge. Otherwise, an analyst looking at today's profile cannot reconstruct what the system knew when yesterday's login was challenged.
I would model four node types: account, verified email identifier, device pseudonym, and login session. Edges carry provenance and age. An email edge created by completed verification is stronger than an address merely typed into a recovery form; a device edge seen five minutes ago is more relevant to current session risk than one last observed many months ago. This is a tracing model, not permission to collect every browser attribute available. Retention and access controls belong in the design review before the first event lands in storage.
The lookup sequence is deliberately asymmetric. First, search for the exact verified address. Next, retrieve candidate accounts that share recent, policy-approved device evidence. Then score each candidate pair using independent observations. Do not rewrite dots, strip plus-tags, or lowercase an entire internationalized address on the assumption that every mail provider interprets those transformations the same way. The safe canonical key is the normalization your own verification and mail-delivery policy can defend; provider-specific aliases can be separate, attributed evidence rather than silent identity equivalence.
That last choice prevents a nasty category error. Identity resolution estimates that records may represent the same actor. Account linking changes authorization state. They need separate thresholds, separate audit events, and usually a fresh proof of control before the second operation.
The incident shape: a shared device is not a shared person
Consider a bounded production scenario. A marketplace login arrives for account B from a device pseudonym recently associated with account A, while an email lookup finds no exact match. The tempting implementation marks B as a duplicate and joins the profiles. Now follow the consequences rather than stopping at the match: B inherits a relationship it cannot explain, the next sensitive action sees the combined history, support receives an appeal with no reproducible decision ID, and an analyst finds only the current account rows because the original observations were overwritten. The first diagnostic question should be whether the same person proved control of both accounts. If the answer is no, the device edge was promoted from correlation to identity without sufficient evidence. That implementation is fast, deterministic, and wrong whenever two people share a tablet, an office kiosk, or a family computer.
Stop there.
The invariant is simple: device evidence can raise session risk, but it cannot prove account ownership. In this scenario, the resolver should emit a candidate relationship and the authentication policy should choose an action. A low-confidence edge may only enrich the investigation view. A medium-confidence cluster can require a verified factor before allowing a sensitive operation. A high-confidence match can offer an explicit account-linking flow, but the merge still needs proof that the user controls both sides. Keep the records separate until then.
The operational failure is not limited to false merges. A resolver can also miss real duplicates when it treats one address string as permanent identity, when it drops evidence after a profile edit, or when it evaluates device signals without event time. Each error has a different cost: false positives lock legitimate users into recovery, false negatives leave promotion abuse or account cycling undetected, and stale positives create delayed surprises that are difficult for support staff to explain.
I initially reach for precision as the primary launch metric here, because an incorrect link has a larger authorization blast radius than an unresolved candidate. But precision alone can hide a resolver that declines almost everything. The review needs both precision and recall on labeled cases, plus the challenge rate and successful challenge rate by cohort. I'm not sure one global threshold can survive the difference between buyer logins, seller payouts, and support-assisted recovery; per-action policy is the evidence-based answer, and a shadow run will show whether those cohorts really diverge.
Score evidence, then keep policy outside the resolver
The resolver should return evidence and a confidence band. It should not decide that a session may withdraw funds, change a payout destination, or merge profiles. That boundary keeps the scoring code testable and gives the authentication service one place to apply product-specific friction budgets.
Here is a small Go example. The weights and time windows are policy inputs for illustration, not universal constants; they must be calibrated against labeled marketplace outcomes and reviewed for disparate impact.
package resolution
import "time"
type Evidence struct {
VerifiedEmailMatch bool
RecentDeviceMatch bool
RecoveryProofMatch bool
ObservedAt time.Time
}
type Result struct {
Score int
Reasons []string
}
func Score(now time.Time, e Evidence) Result {
r := Result{}
if e.VerifiedEmailMatch {
r.Score += 60
r.Reasons = append(r.Reasons, "verified_email_match")
}
if e.RecentDeviceMatch && now.Sub(e.ObservedAt) <= 24*time.Hour {
r.Score += 20
r.Reasons = append(r.Reasons, "recent_device_match")
}
if e.RecoveryProofMatch {
r.Score += 30
r.Reasons = append(r.Reasons, "recovery_proof_match")
}
return r
}
type Action string
const (
Allow Action = "allow"
StepUp Action = "step_up"
OfferLinking Action = "offer_linking"
)
func DecideForLogin(r Result) Action {
switch {
case r.Score >= 80:
return OfferLinking
case r.Score >= 20:
return StepUp
default:
return Allow
}
}
The important property is visible in the code: a recent device match reaches StepUp, not OfferLinking. An exact, verified email match is meaningful but still does not cross the illustrative linking threshold by itself. Two different evidence classes are required. In a real service, I would also return evidence version, model or ruleset version, and an opaque decision ID so an appeal can reproduce the inputs without exposing fingerprint material to every downstream log.
Error handling deserves the same separation. A timeout in candidate retrieval is not evidence that no duplicate exists. Treat it as an unknown resolution state, apply the authentication service's fail-open or fail-closed policy for that specific action, and record the degraded decision. Login may tolerate a bounded step-up; changing payout details probably should not inherit that same posture.
Buy, build, or keep the lookup narrow
There are three credible architecture choices. None wins every workload.
| Approach | On-call and capacity | Control and lock-in | Best fit | Main limitation |
|---|---|---|---|---|
| Exact verified-email lookup | Small state space and a short request path | High control; ordinary data-store semantics | Early systems where duplicate creation is rare and linking is user-confirmed | Misses changed addresses and cannot interpret device evidence |
| Managed identity-resolution service | Provider absorbs much of index scaling and matching operations | Faster adoption, but evidence semantics and exportability require contract review | Teams with limited platform capacity and a need for mature matching workflows | Less control over scoring changes, retention, and incident debugging |
| Self-hosted evidence graph and scorer | Team owns indexing, backfills, hot partitions, and every page | Maximum policy control and portable event history | High-volume systems with specialized risk rules and staff to operate them | Significant on-call load and a continuing calibration program |
Capacity planning should begin with edges, not accounts. Login volume drives event writes; candidate fan-out drives read amplification; retention drives index size; and a popular shared device can create a hot, high-degree node. Put explicit limits on candidate expansion and measure how often the limit truncates a search. Otherwise, the worst possible identity ambiguity also becomes the worst possible query.
The catch is that an evidence graph is not suitable when the team cannot own deletion workflows, threshold calibration, and an appeal trail. Stick with exact verified-email lookup plus an explicit user-controlled linking flow when duplicate volume is low or labels are unreliable. A managed service can be rational when it reduces operational ownership, but only if it can return attributable evidence, support deletion and export requirements, and expose enough decision metadata for incident response. A self-hosted system earns its cost when specialized policy and auditability outweigh the extra on-call surface.
Operate it as a security control with a friction budget
Set two service objectives: one for decision availability and latency, another for decision quality. The first belongs in ordinary SRE telemetry. The second needs delayed labels from successful verification, confirmed abuse, support reversals, and user appeals. Do not collapse them into one green dashboard; a resolver can be fast and consistently harmful.
I would launch in shadow mode, compare candidate decisions with reviewed outcomes, and then enable step-up for a narrow action before offering any account-linking path. Track false-link reports, challenge rate, challenge completion, candidate-set truncation, and unknown decisions. Slice them by action and by the signal combinations that caused the decision, while keeping raw email addresses and device attributes out of general-purpose metrics labels.
Rollbacks need a policy version, not a data rewrite. If a rule raises friction beyond its budget, route new sessions to the previous version and preserve both decision streams for analysis. If a particular evidence source becomes unavailable, mark it unknown; don't convert absence into a negative match. Short-lived caches must include the evidence and policy versions in their keys, or a corrected rule can keep serving yesterday's answer.
Test adversarially. Use fixtures for two people on one device, one person after an email change, a recycled address, concurrent sign-ups, delayed verification events, deletion followed by recreation, and a high-degree shared device. Add property tests asserting that device evidence alone never authorizes a merge and that adding an unrelated weak signal cannot lower required proof for a sensitive action. Then run load tests against deliberately skewed graphs, because uniform synthetic accounts conceal the hot-node behavior that will wake the on-call engineer.
The decision rule is therefore conservative: use resolution to decide how much proof to request, not to declare who a person is. That preserves the security value of correlated signals without making marketplace access depend on a brittle guess.
Top comments (0)