Lista de espera para un nuevo SaaS7 min de lectura
Cómo añadir un formulario de lista de espera en HTML puro a cualquier landing page
Una página de lista de espera completa en HTML puro y sin build que funciona con JavaScript desactivado, muestra un agradecimiento en línea cuando carga y recurre a un plan B cuando hace falta.
Escribe un formulario HTML normal que envíe el email a tu endpoint de Ikiform y redirija a una página de agradecimiento, para que funcione en cualquier hosting sin nada de JavaScript. Luego añade un pequeño script que envíe el mismo formulario con fetch, muestre el agradecimiento en la página y devuelva el control al envío normal cuando el script no pueda terminar, por ejemplo cuando la verificación humana está activa.
Una página de lista de espera no necesita un framework, un paso de build ni un servidor. Cuatro archivos estáticos y un backend de formularios bastan, y la página carga más rápido que cualquier cosa que exporte un constructor. Esta página lo construye todo, capa a capa, para que cada capa funcione por sí sola.
Qué importa sin un paso de build
- El formulario es la integración. Un
<form>normal con unactiones toda la conexión con Ikiform. Sin paquetes, sin clave de API, nada que compilar. - JavaScript es una mejora, no un requisito. La página debería recoger registros aunque el script esté bloqueado, sea lento o falle. Cada capa de abajo se suma a la anterior y vuelve a ella si falla.
- El script nunca debe dejar a nadie atrapado. Si
fetchfalla o Ikiform pide una verificación humana quefetchno puede ejecutar, el script devuelve el formulario al navegador para que lo envíe de la forma normal. - Se aloja en cualquier sitio. GitHub Pages, Netlify, Cloudflare Pages, un bucket de S3 o un hosting compartido sirven estos archivos tal cual.
| Capa | Qué aporta | Si falta |
|---|---|---|
| Formulario HTML | Envía el email, redirige a thanks.html | No funciona nada, por eso va primero |
| CSS | Una página legible en modo claro y oscuro | Un formulario sencillo pero funcional |
| Script | Agradecimiento en la página, errores por campo, sin cambiar de página | El envío normal y la redirección |
- Con JavaScript desactivado, el navegador envía el formulario, Ikiform redirige con un 303 a tu URL de _redirect y el visitante ve thanks.html.
- Con el script cargado, envía con fetch y pide JSON. Ikiform responde 201 o un error, y el script muestra el resultado en la misma página.
- Si fetch falla, o Ikiform responde challenge_required, el script llama a form.submit().
- Ese envío normal muestra la página de verificación humana de Ikiform si hace falta y termina en thanks.html.
Los archivos
La página
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Acme is almost here</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>Acme is almost here</h1>
<p>Invoices that chase themselves. Leave your email and we'll invite you first.</p>
<form id="waitlist" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
<label for="email">Email</label>
<div class="row">
<input id="email" name="email" type="email" autocomplete="email" required placeholder="you@company.com">
<button>Join the waitlist</button>
</div>
<input type="hidden" name="_redirect" value="https://example.com/thanks.html">
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden>
<p class="message" role="status" aria-live="polite"></p>
</form>
</main>
<script src="waitlist.js" defer></script>
</body>
</html>_redirectlleva el navegador athanks.htmldespués de un envío normal. Solo funciona con una URLhttps:en uno de los Allowed domains del formulario, así que añade ahí tu dominio. Redirect after submit en los ajustes del formulario hace lo mismo sin el input oculto.- El input
_gotchaes el honeypot. Las personas nunca lo ven; los bots que rellenan todos los inputs se guardan como spam. aria-live="polite"hace que los lectores de pantalla anuncien los mensajes del script sin mover el foco.- La etiqueta del script usa
defer, así que se ejecuta cuando el formulario ya existe y nunca bloquea el dibujado de la página.
La mejora
const form = document.getElementById("waitlist");
const message = form.querySelector(".message");
const button = form.querySelector("button");
const key = crypto.randomUUID();
form.addEventListener("submit", async (event) => {
event.preventDefault();
button.disabled = true;
message.textContent = "Joining…";
try {
const response = await fetch(form.action, {
method: "POST",
headers: { Accept: "application/json", "Idempotency-Key": key },
body: new FormData(form),
});
const result = await response.json();
if (result.ok) {
form.replaceChildren(Object.assign(document.createElement("p"), {
textContent: "You're on the list. We'll email you on launch day.",
role: "status",
}));
return;
}
if (result.error.code === "challenge_required") {
form.submit();
return;
}
const wait = response.headers.get("Retry-After");
message.textContent = wait
? `Too many tries. Wait ${wait} seconds and try again.`
: result.error.fields?.email?.[0] ?? result.error.message;
} catch {
form.submit();
return;
}
button.disabled = false;
});Accept: application/jsonhace que Ikiform responda con JSON en lugar de una redirección, para que el script pueda mostrar el resultado en su sitio.- La clave de idempotencia se crea una vez por carga de página. Un doble clic, o un reintento tras una respuesta lenta, guarda un solo registro.
challenge_requiredsignifica que la verificación antispam del formulario está en Ikiform yfetchno envió ningún token.form.submit()envía de la forma normal, lo que muestra la página de verificación de Ikiform y luego la de agradecimiento. No vuelve a disparar el eventosubmit, así que no hay bucle.- Un error de red sigue el mismo plan B. Si Ikiform no era accesible, el envío normal muestra el error propio del navegador, que sigue siendo más honesto que un botón atascado.
- Un
429leeRetry-Aftery dice cuánto esperar.
La página de agradecimiento
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>You're on the list</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>You're on the list</h1>
<p>We'll email you the day Acme opens. Nothing before that.</p>
<p><a href="/">Back to the page</a></p>
</main>
</body>
</html>Los estilos
body { margin: 0; font: 16px/1.5 system-ui, sans-serif; color: #111; background: #fff; }
main { max-width: 34rem; margin: 0 auto; padding: 18vh 1rem 4rem; }
h1 { font-size: 2.25rem; line-height: 1.1; margin: 0 0 0.75rem; }
label { display: block; font-weight: 600; margin: 1.5rem 0 0.375rem; }
.row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
input[type="email"] { flex: 1 1 14rem; font: inherit; padding: 0.625rem 0.75rem; border: 1px solid #ccc; border-radius: 0.5rem; }
button { font: inherit; padding: 0.625rem 1rem; border: 0; border-radius: 0.5rem; background: #111; color: #fff; cursor: pointer; }
button:disabled { opacity: 0.6; cursor: progress; }
.message { min-height: 1.5em; margin: 0.5rem 0 0; }
@media (prefers-color-scheme: dark) {
body { color: #eee; background: #111; }
input[type="email"] { background: #1b1b1b; color: inherit; border-color: #444; }
button { background: #eee; color: #111; }
}example.com, actualiza _redirect y el enlace de vuelta. Ikiform ignora un _redirect que no puede seguir y muestra en su lugar su propia página de agradecimiento alojada, así que un desajuste nunca hace perder un registro.Despliega y prueba
Pon los cuatro archivos en una carpeta y súbela a cualquier hosting estático. Luego añade el dominio del hosting en Allowed domains: por ejemplo username.github.io, your-site.netlify.app o tu propio dominio. Mientras trabajas en local, añade también localhost; sirve la carpeta con cualquier servidor estático, porque una página abierta como URL file:// no envía origen.
Comprueba que funciona
- En Allowed domains del formulario, añade el dominio de tu sitio, y
localhostmientras desarrollas. Déjalo vacío para aceptar envíos desde cualquier sitio. - Desactiva JavaScript en el navegador y regístrate. Llegas a
thanks.html. - Vuelve a activarlo y regístrate otra vez. El formulario se cambia por la línea de agradecimiento sin salir de la página.
- Pon Spam check en Ikiform y regístrate una vez más. La página pasa a la verificación y luego a
thanks.html. - Abre Submissions en el panel. El nuevo envío está ahí con todos los campos que mandaste.
- Envía con el honeypot relleno,
_gotcha=bot. Sigue recibiendo una respuesta de éxito, pero el envío se guarda como spam y no manda email ni webhook.
Problemas habituales
| Problema | Qué hacer |
|---|---|
| El navegador llega a la página de agradecimiento de Ikiform en vez de a la tuya | _redirect se ignora salvo que su dominio esté en Allowed domains y sea una URL https:. Añade el dominio, o configura Redirect after submit en los ajustes del formulario. |
403 origin_not_allowed, o todos los envíos acaban en spam | El dominio de la página no está en los Allowed domains del formulario. Añádelo, y también localhost para las pruebas locales. Los subdominios de un dominio de la lista están permitidos. |
| Los envíos desde una página abierta como archivo se rechazan o se marcan como spam | Una página file:// no envía origen. Sirve la carpeta con un servidor estático local y añade localhost a Allowed domains. |
| La línea de agradecimiento nunca aparece, pero los registros llegan | El script no cargó: revisa la ruta del atributo src, o que el hosting sirva archivos .js. El envío normal sigue funcionando, por eso llegan los registros. |
402 test_limit_reached | El espacio de trabajo no tiene plan y ya usó sus 50 pruebas gratuitas desde localhost este mes. Elige un plan, o usa Send test en el panel. |
La documentación de formularios HTML enumera todos los campos reservados, y la plantilla de lista de espera genera una versión con nombre y rol. Exportar la lista y el email de lanzamiento están en la guía principal.
Preguntas
Sí. Un formulario HTML normal envía el email a tu endpoint de Ikiform e Ikiform redirige el navegador a tu página de agradecimiento. JavaScript solo añade el mensaje en la página.
En cualquier sitio que sirva archivos estáticos: GitHub Pages, Netlify, Cloudflare Pages, S3 o un hosting compartido. El formulario envía a Ikiform, así que el hosting nunca ejecuta código.
Ikiform responde 403 challenge_required. El script de esta guía lo detecta y envía el formulario de la forma normal, lo que muestra la página de verificación y luego tu página de agradecimiento.
No. Ikiform no tiene páginas de formulario alojadas. Pones el formulario en tu propio sitio, en cualquier constructor o framework, y lo apuntas a tu endpoint de Ikiform.
No, no hay plan gratuito. Probar es gratis: puedes enviar 50 envíos de prueba al mes desde localhost antes de pagar. Para pasar a producción, Pro empieza en 6 $/mes por 500 envíos, o puedes pagar una sola vez 10.000 envíos por 49 $ o 50.000 envíos por 149 $, con créditos que nunca caducan.
¿Te atascaste en un paso o quieres una guía para tu caso? Escribe a hello@ikiform.com.