5. Crea el formulario
Ahora la parte que vive en tu web: un formulario HTML normal y unas pocas líneas de JavaScript.
Reúne tres valores
Sección titulada «Reúne tres valores»| Qué | Aspecto | Dónde encontrarlo |
|---|---|---|
| Clave pública | pk_… |
Seguridad → Clave pública (botón de copiar), o en la pestaña Fragmentos de la plantilla |
| ID de la plantilla | tpl_… |
Lista de Plantillas, columna ID de la plantilla, o en la pestaña Fragmentos |
| ID del servicio | svc_… |
En la pestaña Fragmentos, o sáltatelo y pasa null para usar tu servicio predeterminado |
Lo más fácil es la pestaña Fragmentos de la plantilla Formulario de contacto. Genera código listo para HTML / JS, React, Vue, Svelte, Node.js y cURL con tu clave, tus ID y todos los parámetros de la plantilla ya rellenados.

Los fragmentos generados llaman a send() con un objeto de valores. Para un formulario es más
sencillo usar sendForm(), que lee los campos directamente del <form>: es lo que hacemos a
continuación.
El formulario
Sección titulada «El formulario»-
Añade el formulario a tu página. Los atributos
nametienen que coincidir con los parámetros de la plantilla:name,emailymessageson obligatorios;phoneytopic, opcionales. -
Pon el widget de Turnstile dentro del formulario. Añade un campo oculto
cf-turnstile-responsequesendForm()envía junto con el resto. Usa la clave de sitio de tu propio widget de Turnstile del paso 4. Si de momento te has saltado la CAPTCHA, no pongas el widget. -
Carga el SDK desde la CDN. Crea un objeto global
frontmail. -
Llama a
frontmail.init()con tu clave pública y afrontmail.sendForm()al enviar.
<form id="contact"> <label>Nombre <input name="name" required maxlength="200" /></label> <label>Correo electrónico <input name="email" type="email" required /></label> <label>Mensaje <textarea name="message" required></textarea></label>
<!-- Turnstile añade el campo oculto cf-turnstile-response --> <div class="cf-turnstile" data-sitekey="YOUR_TURNSTILE_SITE_KEY"></div>
<button type="submit">Enviar</button> <p id="form-status" role="status"></p></form>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script><script src="https://cdn.jsdelivr.net/npm/@frontmail/browser@0.1.0/dist/frontmail.umd.js" integrity="sha384-g2Rxznkz2yb/YYSdVTkXLxZFZ2Nh2+XdhPStxh0stydXFF3gYjDuyHimWIZLJDCc" crossorigin="anonymous"></script><script> frontmail.init({ publicKey: 'pk_…' });
const form = document.getElementById('contact'); const status = document.getElementById('form-status');
form.addEventListener('submit', async (event) => { event.preventDefault(); status.textContent = 'Enviando…'; try { // null = tu servicio predeterminado; o pega el ID svc_… de la pestaña Fragmentos const result = await frontmail.sendForm(null, 'tpl_…', form); // result.status es "queued" (en camino) o "held" (aceptado, se envía cuando vuelva a haber créditos) status.textContent = '¡Gracias! Te responderemos pronto.'; form.reset(); } catch (err) { status.textContent = 'Lo sentimos, no ha funcionado: ' + err.message; } finally { window.turnstile?.reset(); // un token de Turnstile solo vale una vez } });</script>Qué pasa al enviar:
sendForm()envía a Frontmail los campos del formulario, tu clave pública y el token de Turnstile.- Frontmail comprueba el origen, la CAPTCHA y los parámetros, y responde enseguida con
{ messageId, status }. El correo en sí se envía en segundo plano. - Tanto
queuedcomoheldsignifican éxito: el mensaje se ha aceptado y está a salvo. En ambos casos, muestra al visitante un agradecimiento normal. - Si hay un problema, la promesa se rechaza con un error que tiene un
codey unmessagelegible, p. ej.invalid_template_paramscuando la dirección de correo no es válida o falta un campo obligatorio. Los errores de red y los fallos puntuales del servidor se reintentan automáticamente, sin enviar dos veces.
¿Usas un framework?
Sección titulada «¿Usas un framework?»Los paquetes para frameworks incluyen un componente de formulario listo que renderiza el widget de Turnstile y gestiona por ti el estado del envío: