Email Validation
Deliverability verdicts on visitor email addresses: automatic annotation, on-demand checks, and blocking policies
Two ways to validate email addresses against Surface's provider waterfall and the environment's spam blacklist. Both share one per-address verdict cache, so an address is billed once.
What happens on an invalid address is a policy decision
The form owner decides:
| Policy | Behavior | Implementation |
|---|---|---|
| Annotate only (default) | Visitor continues; the response carries the verdict for workflows and scoring | emailValidation: true on init |
| Soft warn | Inline note, visitor continues | validateEmail in your handler, message on !isValid |
| Block | Visitor stays on the step until deliverable | validateEmail gate before next() |
| Disqualify | End on the "not a fit" ending | validateEmail, then disqualify() |
Never gate navigation on validation unless the owner explicitly chose block or disqualify.
Automatic mode
SurfaceForms.init({ ..., emailValidation: true });Validates email answers as captured (debounced) and writes the same verdict keys native forms write (isEmailValid, emailValidation, emailValidationFailed) to the response's metadata. Never blocks navigation or submission. Off by default because verdicts bill the owner's validator quota.
On-demand checks
form.validateEmail(email) resolves the server-side verdict { isValid, status, reason, failed? }:
// Block: keep the visitor on the step with a message.
continueButton.addEventListener("click", async () => {
const { isValid, reason, failed } = await form.validateEmail(emailInput.value);
if (isValid || failed) return form.next(); // fail open: never lock out on a vendor outage
errorEl.textContent = `That address looks undeliverable (${reason}).`;
});For the disqualify policy, call form.disqualify() instead of showing the message. Pages with a gate should carry data-surface-nav="js" so validate_form_html knows navigation is code-driven. Pairing a gate with emailValidation: true is fine; the shared cache bills once.
Verdicts fail open
When no validator can run, the result is { isValid: true, failed: true, status: "unknown" }: an outage must never lock a visitor out. Check failed to soft-warn instead of hard-block, and expect an address accepted today (fail open) to return a definitive invalid tomorrow as the waterfall resolves.
In preview mode, validation resolves synthetic valid verdicts with no network and no billing. form.capabilities.emailValidation reports whether automatic mode is active.