Privacy and retention

See what decision history keeps, what it never stores, and how long it is retained.

SignupScore keeps a limited decision history so you can understand recent signups and investigate a result. Full secret keys and full email addresses are not stored in that history.

Decision events

A decision in the dashboard can include:

Stored itemExample
Domainexample.com
Input typeemail or domain, never the address itself
Decisionallow, review, or block
Main reasonDISPOSABLE
Request ID01J...
Secret key nameProduction
TimeWhen the check happened

No part of the address is kept, not even a masked form of it. A row records the domain and that the input was an email, which is enough to recognize a decision.

Retention windows

PlanDecision historyEmail memory
Free7 days30 days
Paid plans90 days365 days

Records past these windows are deleted automatically. The dashboard may also show summary counts for recent decisions; those totals contain counts only, not email addresses or domain inputs.

We also read this history in aggregate to improve detection: a domain that several unrelated accounts check, and that we hold no intelligence about, is a candidate for review. That analysis looks at domains and counts only, and no customer is identified in the result.

Email memory (mailbox_alias)

The mailbox_alias signal spots when one mailbox reaches you under more than one spelling. It is off until you turn it on.

Turning it on never changes a decision. It adds one field to the response and nothing else, so no signup that gets in today is refused tomorrow because you enabled it.

How it works

  1. Your signup form sends an address, as usual.
  2. We simplify it to one standard spelling. f.o.o+trial2@gmail.com and foo@googlemail.com both become foo@gmail.com.
  3. We save two codes that cannot be read back: one for the simplified address, one for the spelling you sent. The address is not saved.
  4. Next time, any spelling of that inbox finds the same record. A spelling different from the first one we saw returns mailbox_alias: true. The identical address returns false, because that is a retry your own records already cover.
  5. The decision never changes because of it.

Per-provider alias rules are in Signals and reasons.

What it is good for

Your users table sees two different strings. We see one inbox.

They checked in asThey come back asYou getIf that mailbox already signed up
sarah.chen@gmail.comsarahchen+new@gmail.comtrueBlock, a second trial
johnsmith@gmail.comjohn.smith@gmail.comtrueBlock, or offer account recovery
foo@gmail.comfoo@googlemail.comtrueBlock, a second trial
dana@fastmail.comdana+promo@fastmail.comtrueBlock, a second trial
mia@outlook.commia+trial@outlook.comtrueBlock, a second trial
jane.smith@gmail.comj.smith@gmail.comfalseDifferent inbox, let them in
mia@outlook.comm.ia@outlook.comfalseDots count on Outlook
foo@gmail.comfoo@gmail.comfalseSame address, just a retry

The bottom three rows matter as much as the rest. We collapse only spellings a provider documents as one mailbox, never two addresses it treats as different, and never the identical address twice.

The deciding fact is one only you hold: whether that mailbox already has an account. Block when it does. When it does not, leave the decision to your other rules, because the earlier check may never have become a signup.

That lookup needs one column on your side. Save normalized_email as a canonical_email column when a signup completes, put a unique index on it, and query that column rather than the address the person typed. Your table stores what each person entered, so johnsmith@gmail.com will not find a row saved as john.smith@gmail.com unless you saved the canonical form too. The unique index is also what stops two alias signups racing each other through at the same moment.

Row 2, on the second signup
{
"action": "allow",
"signals": {
  "normalized_email": "johnsmith@gmail.com",
  "mailbox_alias": true
}
}

How the fingerprint is built

One address, start to finish
you send     f.o.o+trial2@gmail.com
we simplify  foo@gmail.com
your key     HMAC-SHA-256(our secret, your account id)
we hash it   HMAC-SHA-256(your key, foo@gmail.com)
we save      v1:4c9be0f2a1d83b57e6c2091f7ad4e8b3

Only the last line is saved. The v1 says which version of the rules made it.

The address you sent, f.o.o+trial2@gmail.com, goes through the same step to produce a second code. That one is saved on the first sighting and is what later checks compare against. Equally unreadable.

Nobody can read an address back out of it. A plain hash would not be safe: there are few enough real addresses that anyone could hash a long list and match the rows. The key stops that, and the key is not in the database.

Every account gets a different key. The same address under two customers gives two codes that look nothing alike, so records can never be matched between accounts.

What is stored

A separate data category from decision history, storing strictly less:

Stored itemDetail
Keyed fingerprintThe v1:… value above. Non-reversible, unique to your account.
Spelling fingerprintThe same kind of code for the first spelling seen. Tells a retry from a new alias.
Related derived codesFurther codes of the same kind, used only to measure our own accuracy. Never returned by the API.
First seen, last seen, countTimestamps and a counter.

What is not stored: the email address, the local part, or any reversible form of either.

Records are kept 30 days on Free and 365 on paid plans, counted from the last check of that address; checking it again extends that. Controlled from Dashboard → Protection → Email memory:

  • It starts off. New accounts record nothing until you turn it on.
  • Turn it off at any time. Checks then return mailbox_alias: null, nothing new is recorded, and stored records expire on their own.
  • Forget a specific email. Enter the address; we fingerprint it transiently, delete the match, and discard the address.
  • Account deletion removes all memory records with the account.

API keys

SignupScore shows a new secret key once. After that, the dashboard keeps only what it needs to recognize and display the key. Revoking the key stops it from authenticating later requests.

Account deletion

For a privacy or account-deletion request, contact hi@hreflabs.com. We will confirm the account, remove the associated product data, and explain any records that cannot be removed immediately.