Signals and reasons

Understand why a decision was returned and when individual signals matter.

The action tells your signup flow what to do. primary_reason says why, and the signals are the facts behind it.

Primary reason

primary_reason is the one thing that most drove the action. It is OK when nothing applied.

This shortened response says the address was blocked because its domain is a known disposable provider:

Decision example
{
  "action": "block",
  "primary_reason": "DISPOSABLE",
  "signals": {
    "disposable": true
  }
}

Use it for a short explanation, or to record why a signup was stopped. It is always one of these:

ValueWhat it means
DOMAIN_NEWLY_REGISTEREDYour new-domain rule.
INFRASTRUCTURE_ZONEYour free and dynamic-DNS rule.
DOMAIN_UNDELIVERABLEYour undeliverable-domain rule.
DOMAIN_TYPO_SUGGESTEDThe domain looks like a misspelling of a known provider.
DISPOSABLEA known disposable or temporary email provider.
NO_MXThe domain has no mail servers.
SUSPICIOUSThe address looks like generated junk or a test address.
ROLE_BASEDA shared address such as info@, admin@, or support@.
PRIVACY_RELAYA relay that forwards to another inbox. block only if you turned the relay rule on.
PUBLIC_PROVIDERA public provider such as Gmail or Yahoo.
OKNothing. The address looked ordinary.

The table is in priority order. Compare the value, not the wording: the descriptions can change, the values will not.

primary_reason is the single most useful thing to say about the address, which is not always the rule that produced action. Typo domains get squatted, so a mistyped address often lands on a domain that is genuinely disposable. gmial.com returns action: "block" with primary_reason: "DOMAIN_TYPO_SUGGESTED" and suggested_domain: "gmail.com", because "did you mean gmail.com?" is what the person needs to read. signals.disposable is still true, so the cause is never lost. Read the signals when you need the cause rather than the explanation.

Exact domain rules are the exception. They set action without a value of their own, so an allow rule on a disposable domain still reads DISPOSABLE, and a block rule on an ordinary domain reads OK. action is what your rule changed.

Signals

Signals are the facts found during the check. Every field is always present. Use them when your product needs an exception the action alone cannot express.

SignalExampleWhat it tells you
disposablename@tempmail.comThe domain is a known temporary email provider.
public_providername@gmail.comThe address uses a public provider. This is not bad by itself.
role_basedsupport@company.comThe address looks shared by a team or function.
suspiciousA strong junk-like patternThe address needs more confidence before valuable access.
privacy_relayx8f2k9@duck.comA hidden address forwarding to a real inbox. One flag for every relay service, so it does not say which. Allowed unless you turn on the relay rule.
dynamic_dnsme@bob.duckdns.orgThe domain is on a zone where anyone can claim a free subdomain. Blocked unless you turn off the free and dynamic-DNS rule.
has_mxtrueMail server records were found for the domain.
suggested_domaingmail.comThe entered domain may contain a typo.
normalized_emailfoobar@gmail.comThe canonical form after provider alias rules (see below).
mailbox_aliastrueYour account saw this mailbox before under a different spelling. Resending the identical address is false. Off by default.
has_spftrueThe domain publishes an SPF record. Read the caveats below.
has_dmarcfalseThe domain publishes a DMARC record. Read the caveats below.

has_mx, has_spf, has_dmarc, and mailbox_alias can be null, which means not known rather than no. suggested_domain and normalized_email are null when they do not apply.

Correcting a typo

A typo outranks the cause only when the suggestion is confident: exactly one edit away, and not a character glued onto an intact provider name. gmial.com qualifies. xgmail.com and abmail.com do not, so those keep DISPOSABLE as the reason and carry the suggestion in signals.suggested_domain instead.

If you would rather not depend on that distinction, branch on the signal directly and the wording is right in every case:

if (result.signals.suggested_domain) {
  showDidYouMean(result.signals.suggested_domain);
} else {
  explainUsing(result.primary_reason);
}

This applies whatever the action is. A typo on a domain that is not disposable comes back as allow, and the same branch offers the correction before you accept the address.

Email normalization and mailbox_alias

Some providers treat many spellings as one inbox. f.o.o.bar+trial2@gmail.com and foobar@googlemail.com both reach foobar@gmail.com. normalized_email gives you that single form.

  • Gmail, Proton, Outlook, Fastmail, iCloud, Zoho, Yandex, mailbox.org, and Runbox have alias rules we apply.
  • Yandex and Runbox also fold their alternate domains, so alice@ya.ru and alice@yandex.ru are one address, as are demo@rbox.me and demo@runbox.com.
  • Every other domain is only lowercased and trimmed.
  • Two different mailboxes are never merged into one.

mailbox_alias is true when your account has seen the same mailbox before under a different spelling. Off by default; turn it on in Dashboard → Protection → Email memory.

  • Resending the identical address returns false. That is a retry, and your own records already answer it. A new spelling of a mailbox you have seen is the part only we can answer.
  • It proves a previous check of an alias, not an existing account and not a completed signup. Never refuse on the flag alone. Refuse when your own users table confirms that mailbox already signed up, which is the second trial you are trying to stop.
  • It never changes action. What to do about a repeat is your call.
  • All your API keys share one memory, so rotating a key loses nothing. Test addresses are remembered too; use Dashboard → Try a check to avoid that.
  • Turn it off or delete a single address under Dashboard → Protection → Email memory. Details in Privacy and retention.

Sender authentication: has_spf and has_dmarc

Whether the domain publishes SPF and DMARC records.

  • null means not known yet, not "no". The first check of a new domain returns null and a later one has the answer.
  • They describe whether a domain can be spoofed as a sender, which is a different question from whether a signup is real.
  • Real businesses and disposable providers publish SPF at about the same rate, so has_spf on its own tells you little.
  • Do not reject on missing records. Plenty of legitimate small businesses have never set up DMARC.
  • Both are data only and never change action. They are most useful as corroboration alongside other signals.