Skip to content
SimpleCold

API docs

Integrations

Form protection

On this page

Form protection checks the email field on your own sign-up, contact and checkout forms while the visitor types. Add one script tag and a data-simplecold attribute to the field, and SimpleCold stops disposable addresses, catches typos like [email protected] with a "Did you mean …?" link, and (in full mode) rejects mailboxes that don't exist, before the form is submitted.

It uses a publishable key (sc_pub_…), not your secret API key. A publishable key is safe to put in public HTML: it only works on the domains you allow, it can only check addresses, and it never returns more than a short verdict.

Basic and full mode

Every publishable key has a mode:

ModeWhat it checksCost
basicThe address format, disposable and throwaway domains, shared addresses like info@, and common domain typos.Free. Answers instantly.
fullEverything in basic, plus whether the mailbox exists and whether the domain accepts every address.1 credit per unique address.

In full mode the result for an address is cached for 30 days. A visitor who submits the same address twice, or a second form that sees an address already checked in the last 30 days, doesn't use another credit.

Full mode answers within about 1.5 seconds. If the visitor's mail server is slow to answer, the widget gets the reason unverified and lets the address through, so a slow mail server never holds up your form.

Create a publishable key

  1. In the dashboard, open API keys and choose the Form protection tab.
  2. Click Create publishable key.
  3. Give it a name, choose Basic or Full, and add the domains where your form lives.
  4. Choose what happens for each kind of address: Block the form, Warn, but allow, or Allow.
  5. Optionally set a monthly check limit and a Cloudflare Turnstile secret (see Bot protection).
Kind of addressDefaultModes
DisposableBlockBasic and full
Shared (info@, sales@, …)WarnBasic and full
Risky (accept-all domains, mailboxes we can't fully confirm)WarnFull only
Undeliverable (no such mailbox or domain)BlockFull only

Invalid addresses and likely typos are handled in every mode. Only the owner and admins of an organization can create, edit and delete publishable keys; every member can see them and their usage. You can have up to 10 publishable keys per organization.

Allowed domains

The widget only answers requests from pages on the domains you allow. Each entry is either:

  • an exact host, such as example.com or www.example.com, or
  • a wildcard, such as *.example.com, which matches every subdomain (app.example.com, www.example.com, eu.app.example.com).

A wildcard does not include the domain itself: *.example.com doesn't match example.com. If your form is on both, add both. Leave out the scheme, port and path. Add localhost to try the widget on your own machine. A key can have up to 20 allowed domains.

Install the snippet

Copy the snippet from the key's page in the dashboard, or use this one with your publishable key:

Install
<script src="https://simplecold.com/api/v1/widget/v1.js" data-key="sc_pub_XXXX" defer></script>
<input type="email" name="email" data-simplecold>

Add the script once per page, anywhere in the HTML. Then add data-simplecold to every email input you want checked. The script is under 8 KB and has no dependencies.

When the visitor enters an address, the widget checks it and shows a short message next to the input. If the verdict is block, the submit is stopped until the address is fixed.

Script options

Set these as data attributes on the <script> tag:

AttributeDefaultWhat it does
data-key–Required. Your publishable key.
data-selectorinputs with data-simplecoldA CSS selector for the inputs to protect, for example form#signup input[name=email].
data-block-submittrueSet to "false" to never stop a submit and only show messages.
data-turnstilefalseSet to "true" to send the form's Cloudflare Turnstile token with each check. Needs a Turnstile secret on the key.

Messages

The default messages are:

ReasonMessage
invalidPlease enter a valid email address.
typoDid you mean [email protected]?
disposablePlease use a permanent email address, not a disposable one.
rolePlease use a personal address rather than a shared one like info@ or sales@.
riskyWe could not fully confirm this address. Please double-check it.
undeliverableThis email address does not seem to exist.

Change any of them with a data-msg-<reason> attribute on the input:

Custom messages
<input
  type="email"
  name="email"
  data-simplecold
  data-msg-disposable="Please sign up with your work email."
  data-msg-role="Please use your own address, not a team inbox."
>

Styling

The message is a <div class="sc-fp-msg" data-verdict="warn" aria-live="polite"> shown next to the input. data-verdict is ok, warn or block. Change the look with these CSS variables on any ancestor, for example :root:

VariableDefault
--sc-fp-ok#15803d
--sc-fp-warn#b45309
--sc-fp-block#b91c1c
--sc-fp-font-size13px
--sc-fp-gap4px
--sc-fp-linkinherit
Brand colours
:root {
  --sc-fp-warn: #9a6700;
  --sc-fp-block: #c2410c;
  --sc-fp-font-size: 14px;
}

Events

The widget dispatches two DOM events on the input. Both bubble, so you can listen on the input, the form or document.

EventdetailWhen
simplecold:verdict{ email, allow, verdict, reason, suggestion }Every time an address is checked.
simplecold:suggestion{ from, to }The visitor clicked "Did you mean …?" and the input was corrected.
Listen for verdicts
const form = document.querySelector("#signup");

form.addEventListener("simplecold:verdict", (event) => {
  const { email, allow, verdict, reason, suggestion } = event.detail;
  // Turn the submit button off while the address is blocked.
  form.querySelector("button[type=submit]").disabled = !allow;
  if (verdict === "warn") console.log(`${email}: ${reason}`);
});

form.addEventListener("simplecold:suggestion", (event) => {
  const { from, to } = event.detail;
  analytics.track("email_typo_fixed", { from, to });
});

The check endpoint

The widget calls these public endpoints. You don't need to call them yourself, but they are documented so you know exactly what leaves the page.

MethodPathWhat it does
GET/api/v1/widget/v1.jsThe script.
POST/api/v1/widget/sessionStarts a page session: { key, turnstileToken? } returns the first { nonce }.
POST/api/v1/widget/checkChecks one address and returns the verdict and the next nonce.
POST/api/v1/widget/eventRecords that the visitor accepted a suggestion: { key, event: "suggestion_accepted", nonce }.
POST/api/v1/widget/check

They need no API key and no session cookie. Requests must come from a page on one of the key's allowed domains: the Origin (or Referer) header is checked against the list.

Request
{
  "key": "sc_pub_XXXX",
  "email": "[email protected]",
  "nonce": "…"
}

turnstileToken is added when the script has data-turnstile="true".

Verdict · 200
{
  "allow": true,
  "verdict": "warn",
  "reason": "typo",
  "suggestion": "[email protected]",
  "nonce": "…"
}
FieldTypeDescription
allowbooleanfalse only when the verdict is block.
verdictstringok, warn or block, from the key's rules.
reasonstringok, invalid, typo, disposable, role, risky, undeliverable or unverified.
suggestionstring or nullThe corrected address for a likely typo, for example [email protected].
noncestringThe nonce for the next request (see Bot protection).

The answer is deliberately minimal. It never includes the full verification result, the mail server, or anything else about the address. Use the single validation endpoint with your secret API key on your server if you need the full result.

Fails open

The widget never breaks your form. If our service is slow or unreachable, a request fails, the key is turned off, its monthly limit is reached, or the visitor is rate limited, the form submits as normal without a check. In full mode, a mailbox that doesn't answer within about 1.5 seconds comes back as unverified with allow: true.

Because the widget runs in the visitor's browser, a determined visitor can always bypass it. If you need a guarantee, also check the address on your server with your secret API key.

Rate limits

The widget endpoints are rate limited per publishable key and per visitor IP address, so one visitor can't use up your monthly checks or credits. Normal visitors never get near the limits. A rate-limited request gets a 429, and the widget lets the form submit without a check.

The monthly check limit on the key caps how many addresses it checks in a calendar month (10,000 by default; you can raise, lower or remove it). After the limit, the form keeps working without checks until the next month. In full mode, this is also your ceiling on credits spent by the form.

Bot protection

A publishable key is public, so every check is protected against scripted abuse:

  • Signed, single-use nonces. The widget first asks for a session nonce, and every response carries the next one. Each nonce is signed, tied to your key and usable once, so requests can't be replayed or made outside a real page session.
  • Bring your own Turnstile (optional). If your form already uses Cloudflare Turnstile, paste your Turnstile secret into the key and add data-turnstile="true" to the script. The widget then sends the form's cf-turnstile-response token with each check, and SimpleCold verifies it with Cloudflare before checking the address. The secret is never shown again after you save it; you can replace or clear it from the key's settings.
Form protection | SimpleCold API docs