Skip to main content

In-Flight Email Validation

In-flight email validation checks an email address as the visitor types it, then shows an inline, friendly message when the address looks wrong — before the form is ever submitted. It helps your visitors fix typos, discourages disposable addresses, and improves the quality of the leads that reach you.

The feature is off by default and is enabled from markup — no backend changes required. When enabled, the script detects the email field, waits for the visitor to pause after a plausibly complete address is typed, then asks Opportify for a minimal verdict and renders a message you can fully customize.

Powered by Email Insights

Each validation is answered by Opportify Email Insights and is billed against your Email Insights balance — not your Fraud Protection balance. The script only sends a request once a minimally valid address has been typed, and layered rate limiting protects the endpoint from abuse so your balance is spent only on genuine checks.


How it works​

Visitor types in the email field
└─ once a plausibly complete address is present, the script waits (debounce)
└─ POST /intel/v1/validate-email (signed, session-bound, origin-checked)
└─ Email Insights verdict → { emailType, isDeliverable, riskLevel, riskScore, reasons }
└─ inline message shown next to the field (only when something needs attention)

The validation request is tied to the visitor's existing Opportify session and signed, exactly like every other call the script makes. The response carries a curated set of Email Insights signals (classification, deliverability, risk, and the risk factors) — never raw SMTP dialogue or the provider slug.


Enabling it​

Globally, for every form on the page​

Add data-opty-email-validation to the Opportify <script> tag. Every form on the page that contains an email field is then validated in-flight.

<script
src="https://cdn.opportify.ai/f/v1.3.8.min.js"
data-opportify-key="pk_live_abc123xyz"
data-opty-email-validation
async
></script>

Per form​

When the global attribute is absent, opt a single form in by adding data-opty-email-validation="on" to the <form> element:

<form action="https://form.opportify.ai/intel/v1/submit/YOUR_ENDPOINT_ID" data-opty-email-validation="on">
<input name="email" type="email" />
<button type="submit">Sign up</button>
</form>

When the global attribute is present, a single form can opt out with data-opty-email-validation="off":

<form data-opty-email-validation="off">
<input name="email" type="email" />
</form>
Works even without submit interception

In-flight validation is independent of Opportify's proxied form submission. You can enable it on a form you submit yourself — the attribute is all that is needed to signal intent.


Which field is validated?​

The script validates every email field it finds in each opted-in form — a form with two email inputs gets both instrumented. A field is treated as an email field when it is an <input type="email">, has autocomplete="email", or has a name/id that references an email. The name/id/autocomplete fallback only applies to inputs whose type is text (or unset) — an input with another type such as tel or search is not matched even if it is named email. Hidden, disabled, read-only, and password fields are always ignored.


The verdict​

The endpoint returns the curated Email Insights signals — there is no coarse status field. The consumer decides how to present them.

FieldValuesDescription
emailTypeprivate, free, disposable, unknownHow the address is classified
isDeliverableyes, no, unknownReal-time deliverability (always unknown outside production, where SMTP probing is disabled)
isFormatValidtrue, falseWhether the address passes syntax validation
suggestionstringA confident auto-correction (e.g. user@gmail.com for a gmial.com typo), when one exists
riskLevellowest, low, medium, high, highestAI risk bucket — present when the risk report is available
riskScore200–1000AI risk score — present when the risk report is available
reasonsstring[]The AI risk report's baseAnalysis factors — the human-readable signals behind the score

emailType, isDeliverable, and isFormatValid are always present. suggestion, riskLevel, riskScore are present only when available; on a transient fallback reasons is [].

When the built-in message is shown​

The inline message is written to help the visitor fix a likely mistake, not to accuse. It appears only on an actionable signal, in this order (highest first):

  1. a confident auto-correction → a clickable "Did you mean user@gmail.com?" that fills the field and re-checks on tap;
  2. a malformed address (isFormatValid: false) → a gentle "is this correct?" prompt;
  3. an undeliverable mailbox (isDeliverable: "no") → "we couldn't verify this, is it spelled right?";
  4. a disposable address (emailType: "disposable") → a helpful policy note;
  5. a high/highest riskLevel → a neutral "double-check" prompt.

Everything else is silent, keeping the experience unobtrusive and never blocking the visitor on an indeterminate result.

Visible to the browser

Because these signals are returned to the page, riskScore and riskLevel are readable in the browser's network traffic. They are intended to inform client-side UX, not to gate access on their own — keep any hard accept/reject decisions on your backend using the full Email Insights API.

Reacting to the result in JavaScript​

After each completed validation the script dispatches an opportify:email-validation event on window. Its detail carries the field name and the full verdict, so you can drive your own UX (analytics, progressive disclosure, submit gating) without touching the built-in inline message.

window.addEventListener('opportify:email-validation', (event) => {
const { field, result } = (event as CustomEvent).detail;
// result = { emailType, isDeliverable, reasons, riskLevel?, riskScore? }
if (result.riskLevel === 'highest' || result.isDeliverable === 'no') {
// e.g. ask for a second contact method, or soft-gate the submit button
}
});

Customizing the messages​

Every message is customizable. Set an override on the <form> (highest precedence) or on the <script> tag (page-wide default) using data-opty-email-msg-<key>.

<form data-opty-email-validation="on"
data-opty-email-msg-suggestion="Did you mean {suggestion}?"
data-opty-email-msg-typo="Hmm, that email doesn't look right. Mind checking it?"
data-opty-email-msg-disposable="Please use a permanent email address so we can reach you.">
<input name="email" type="email" />
</form>
AttributeShown whenDefault message
data-opty-email-msg-suggestionA confident auto-correction existsDid you mean {suggestion}?
data-opty-email-msg-typoThe address is malformed (isFormatValid: false)Is this email address correct? It doesn't look quite right.
data-opty-email-msg-undeliverableAn undeliverable mailbox (isDeliverable: "no")We couldn't verify this email. Is it spelled correctly?
data-opty-email-msg-disposableA disposable address (emailType: "disposable")We don't accept disposable email. Please use a permanent email address so we can reach you.
data-opty-email-msg-high-riskA high or highest riskLevelPlease double-check this email address.
data-opty-email-msg-genericFallbackPlease double-check this email address.

The suggestion message must contain the {suggestion} placeholder exactly once — the script replaces it with the corrected address and renders that part as a clickable button (styled with .opportify-email-suggestion) that fills the field and re-checks when the visitor taps it. The renderer splits the message on the resolved address, so a template that repeats the placeholder (e.g. Did you mean {suggestion}? (we think you meant {suggestion})) drops everything after the first occurrence; keep it to a single {suggestion}.

Adjusting the debounce​

Control how long the script waits after the visitor stops typing before validating, with data-opty-email-validation-debounce (milliseconds) on the form:

<form data-opty-email-validation="on" data-opty-email-validation-debounce="1200">
<input name="email" type="email" />
</form>

The default is 900 ms. Values are clamped to the 400–5000 ms range, and a non-numeric value falls back to 900 ms — so data-opty-email-validation-debounce="100" is treated as 400 ms and "10000" as 5000 ms.


Styling and customization​

The script ships a small, self-contained stylesheet (injected once into <head> as #opportify-email-validation-styles) that provides the default error treatment:

  • a soft red highlight and focus ring on the email field (.opportify-email-invalid),
  • a trailing error icon painted inside the field (as a background image on the input itself — no wrapper element and no DOM reparenting),
  • a compact message below the field with a leading alert icon and a gentle fade-in (.opportify-email-message).

The input gets aria-invalid="true" and is linked to the message via aria-describedby for screen readers (any existing aria-describedby the host set is preserved). When the input is inside a <label>, the message is inserted after the label so it is not folded into the field's accessible name.

Every colour, size, and spacing value is a CSS custom property on :root, so you can restyle the whole treatment by overriding a variable in your own stylesheet — no !important needed. These are the shipped defaults:

:root {
--opty-ev-error: #dc2626; /* field border + icon colour */
--opty-ev-error-strong: #b91c1c; /* stronger error accent */
--opty-ev-error-bg: rgba(220,38,38,0.06); /* field background tint */
--opty-ev-ring: rgba(220,38,38,0.18); /* focus ring colour */
--opty-ev-message-color: #b91c1c; /* message text colour */
--opty-ev-suggestion-color: #1d4ed8; /* "Did you mean" link colour */
--opty-ev-suggestion-hover: #1e40af; /* "Did you mean" link hover */
--opty-ev-message-size: 0.8125rem; /* message font size */
--opty-ev-message-gap: 0.375rem; /* gap below field + icon gap */
--opty-ev-radius: 6px;
--opty-ev-icon-size: 18px; /* trailing icon size */
--opty-ev-transition: 140ms ease; /* field + icon transition */
}

Dark mode is handled automatically via prefers-color-scheme, which redefines --opty-ev-error, --opty-ev-error-strong, --opty-ev-error-bg, --opty-ev-ring, --opty-ev-message-color, --opty-ev-suggestion-color, and --opty-ev-suggestion-hover with lighter values. If you override one of these in :root without supplying your own dark @media (prefers-color-scheme: dark) block, your value applies in both themes and you lose the automatic dark treatment for that variable. The entrance animation respects prefers-reduced-motion.

Overriding the classes directly​

For deeper changes, target the stable class names. Opportify's rules use single-class selectors (low specificity), so your plain rules win:

ClassElement
.opportify-email-invalidThe email <input> while in the error state (the trailing icon is painted on it as a background image)
.opportify-email-messageThe message element shown below the field
.opportify-email-suggestionThe clickable "Did you mean …?" correction inside the message
/* Example: swap the trailing icon off and underline the field instead */
.opportify-email-invalid { background-image: none; border: none; border-bottom: 2px solid #dc2626; box-shadow: none; }

Shipping your own styles instead​

To disable Opportify's injected stylesheet entirely and style the classes yourself, add data-opty-email-validation-styles="off" to the Opportify <script> tag. The class names and ARIA attributes above are still applied, so your CSS has everything it needs.

<script
src="https://cdn.opportify.ai/f/v1.3.8.min.js"
data-opportify-key="pk_live_abc123xyz"
data-opty-email-validation
data-opty-email-validation-styles="off"
async
></script>

Privacy and abuse protection​

  • The request is signed and bound to the visitor's active session and allowed origin, so it cannot be replayed or forged from another site.
  • The endpoint enforces layered rate limiting — per IP address, per network range, per device, and per session — so your Email Insights balance is spent only on legitimate checks.
  • The response is a curated subset of Email Insights — emailType, isDeliverable, the AI riskLevel/riskScore, and the risk report's reasons factors. It never includes raw mail-server dialogue or the provider slug. Treat the risk fields as UX guidance; keep hard accept/reject decisions on your backend.

See also​