Plate № 28 · UX · forms / resilience

A pattern from the gf.cx specimen book

Antifragile contact form · one email escape hatch for every failure

Source · Dan, 2026-07-28

"Graceful error detection and messaging can help smooth over minor issues."

A contact form looks like one thing and is really a chain: a dialog, a bot-challenge widget, an API function, an email provider. Every link can fail silently — and when one does, the visitor sees a green "sent!" while nothing arrives. The lead is lost with no error anywhere. This pattern makes the chain degrade instead of break: whatever fails, the message still reaches you.

Two failures that looked like mail bugs

Both cost real messages on gf.cx before the fix, and both read as "DNS / DKIM / the email provider is broken" when the cause was entirely front-end:

Explicit render + token gating

The rule for any challenge widget inside a modal: render it explicitly when the dialog opens, capture the token in a callback, and never POST an empty token.

var tsId = null, tsToken = '';
tsId = turnstile.render('#cf-turnstile-slot', {
  sitekey: TS_SITEKEY, size: 'normal',
  callback:           function (t) { tsToken = t; },
  'expired-callback': function ()  { tsToken = ''; },
  'error-callback':   function ()  { tsToken = ''; markTsUnavailable(); }
});
// on submit — gate, don't guess:
if (!tsToken) { fail('One sec — finishing the human-check, then tap Send.'); return; }

Prove the secret independently before blaming it: call the challenge siteverify endpoint with the widget's real secret and a dummy token — invalid-input-response means the secret is valid for the widget; invalid-input-secret means a mismatch. Centering has two traps of its own: the widget renders as a fixed ~300px box, so use align-self:center in a flex column; and a modal <dialog> centers via the UA's position:fixed; margin:auto; inset:0, so never set position:relative on it — that drops it to the top-left.

The shape · one escape hatch for every failure

The core principle — borrowed from the ops baseline, remove the coupling, not the component — is that the contact capability must not depend on any single link being up. Every terminal failure funnels to a prefilled, content-preserving mailto:: the visitor's own mail client, message intact, bypassing the widget AND the provider AND your function at once.

FailureDetectionDegrades to
No JS / no <dialog>feature checkthe icon was always a plain mailto:
Challenge CDN down<script onerror> + 5s render timeoutwidget → calm note; Send → mail client
Function / provider downnon-ok responseclickable mailto, message preserved
Network dead / hungAbortController 15ssame
Repeated verify failscounter ≥ 2treat challenge as broken → mail

Safety: build the fallback link with static link text and an encodeURIComponent href — never route user input through innerHTML.

Everyday errors, caught gently

The escape hatch is for catastrophe. Most of what a form actually meets is a typo'd email or an empty field — so a second tier catches those at the field, before any doomed round-trip:

The rule across the two tiers: detect early, message specifically, recover quietly — and reserve the alarming "email us directly" copy for genuine outages.

When it breaks

Reference

Origin
Two "sent but nothing arrived" bugs on gf.cx's contact form (2026-07-28) — honeypot autofill, then a Turnstile-in-dialog empty token — cracked by a live wrangler pages deployment tail on the full deployment UUID. Full write-up on kb.gf.cx.
Kin
The ops baseline's remove the coupling, not the component — antifragility by decoupling from any single dependency — turned on a reader-facing form instead of an unattended job.
Rule
Two tiers: catch the everyday at the field, funnel the catastrophic to one email escape hatch. A form that can't send should still let the message through.