Overview
Form protection is designed for SaaS signups, newsletters, lead capture, free trials, waitlists, and checkout forms. Each integration has a public site key, an exact or wildcard origin allowlist, an independent rate limit, usage analytics, and its own revocation boundary. It never exposes a developer API key.
Syntax, domain, MX, and disposable-domain checks run before submission.
A hidden honeypot catches automated form fillers without adding a visual challenge.
Forms added later by React, Vue, modals, and page transitions are protected automatically.
https://api.mailtooth.com/form-protection/embed.jshttps://api.mailtooth.com/form-protection/verifyQuickstart
- Open Form protection and choose Protect a form.
- Name the integration and add each origin that hosts the form, such as
https://example.com. Origins contain a scheme, host, and optional port, never a path. - Select verification depth and a blocking policy. Quick is the lowest-latency default and costs 0.5 credit per conclusive check.
- Copy the generated script tag before the closing body tag.
<script src="https://api.mailtooth.com/form-protection/embed.js" data-mailtooth-key="YOUR_PUBLIC_SITE_KEY" defer></script>By default, every form containing an input[type="email"] is protected. Set custom selectors in the dashboard when only specific forms should be intercepted.
What the script does
- Finds matching forms on load and whenever the DOM changes.
- Adds an off-screen, non-tabbable honeypot field.
- Verifies on email-field blur and caches the decision for that exact value.
- Pauses submission until a decision is available and preserves each submit button's previous disabled state.
- Uses native
setCustomValidityandreportValidityfor accessible validation. - Resubmits with
requestSubmit, preserving the clicked submit button and existing submit handlers.
Script configuration
| Attribute | Default | Description |
|---|---|---|
data-mailtooth-key | Required | Public site key shown in the dashboard. |
data-mailtooth-form | form | CSS selector for forms to protect. |
data-mailtooth-email | input[type="email"] | Email-field selector resolved inside each matching form. |
data-mailtooth-fail-open | true | Set to false to block while verification is unavailable. |
data-mailtooth-timeout | 12000 | Timeout in milliseconds; the minimum is 1000. |
data-mailtooth-endpoint | Derived from script URL | Optional override for a controlled proxy or staging API. |
<script src="https://api.mailtooth.com/form-protection/embed.js" data-mailtooth-key="YOUR_PUBLIC_SITE_KEY" data-mailtooth-form="#signup" data-mailtooth-email="input[name=email]" data-mailtooth-fail-open="false" defer></script>To render errors in your own element, add data-mailtooth-error="element-id" to the email input. The script updates that element and marks it as an ARIA live region.
Blocking policy
Invalid syntax, missing domains or MX records, disposable domains, and conclusive undeliverable mailbox results are always blocked. An integration can additionally block the following outcomes.
| Option | When it applies | Recommendation |
|---|---|---|
| Risky | Deepcheck found a mailbox that may accept mail but carries delivery risk. | Enable for abuse-sensitive trials and monitor conversion. |
| Unknown | A required DNS, provider, or SMTP check was inconclusive. Unknown checks are refunded. | Leave disabled for revenue forms; enable for strict gated access. |
| Role based | The local part is shared, such as info, admin, or sales. | Enable when an individual work identity is required. |
Quick costs 0.5 credit, Standard costs 1 credit, and Deepcheck costs 2 credits. Unknown outcomes are refunded through the same ledger as API verification.
Browser events
Events bubble from the protected form. Use them for analytics or presentation changes, not as server-side authorization signals.
| Event | Meaning |
|---|---|
mailtooth:verified | The address passed the configured policy. detail contains allowed, reason, and message. |
mailtooth:blocked | The address was blocked. detail contains allowed, reason, and message. |
mailtooth:error | Verification was unavailable. detail.allowed reflects fail-open or fail-closed behavior. |
document.querySelector("#signup").addEventListener("mailtooth:blocked", (event) => {
analytics.track("signup_email_blocked", { reason: event.detail.reason });
});SPAs and dynamic forms
No reinitialization is needed after client-side navigation or modal rendering. The embed observes added elements and enhances each form once. Remove duplicate script tags so a form is not managed by multiple site keys.
Existing synchronous and asynchronous submit listeners still run after verification. The normal browser submission path is retained, including the original submitter when supported.
Security and privacy
- The site key is public by design. It can only request policy decisions and cannot call developer, billing, team, or bulk APIs.
- Browser requests must match the stored origin allowlist. Use separate integrations for production, staging, and local sites.
- Only the email, public site key, and honeypot value are sent to MailTooth. Raw verification evidence is not returned to the page.
- Revoke a copied or retired integration immediately. Its earlier analytics remain available.
Failure behavior
The default is fail open: if verification times out or is unavailable, submission continues so an outage does not lose a signup or purchase. Set data-mailtooth-fail-open="false" for a strict flow. Fail-closed mode shows a retry message and prevents submission.
In-flight checks are canceled when the address changes. A per-integration rate limit returns HTTP 429 when exceeded; the script then applies the selected failure behavior.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| 403 response | The page origin is not allowed. | Add the exact scheme, hostname, and port. Do not add a path. |
| 401 response | The integration is revoked or the site key is wrong. | Copy the current snippet or create a replacement integration. |
| 429 response | The per-minute integration limit was exceeded. | Increase the limit or reduce repeated attempts. |
| Form is not intercepted | The form or email selector does not match. | Inspect the markup and update both selectors in the dashboard. |
| CSP blocks the script or request | The API origin is missing from Content Security Policy. | Allow https://api.mailtooth.com in script-src and connect-src. |
| Submission proceeds during an outage | Fail-open is the default. | Set data-mailtooth-fail-open to false if the strict tradeoff is acceptable. |