Formulario de contacto en un sitio estático

Formulario de contacto en un sitio estático7 min de lectura

Añade un formulario de contacto a un sitio de GitHub Pages

GitHub Pages no ejecuta código de servidor, así que un POST de formulario recibe 405 Not Allowed; envía a un backend de formularios y configura bien las redirecciones del project site.

Preet SutharFundador, Ikiform

GitHub Pages solo sirve archivos estáticos, así que un formulario que envía a tu propio sitio recibe 405 Not Allowed y el mensaje se pierde. Pon un endpoint de Ikiform en el action del formulario, añade username.github.io y tu dominio personalizado, si tienes, a Allowed domains, y redirige a una página de agradecimiento que incluya la ruta de tu project site. No cambia nada en tu repositorio ni en los ajustes de Pages, y ningún secreto acaba en el repositorio público.

Qué cambia en GitHub Pages

GitHub Pages publica lo que genera tu repositorio o tu workflow de Actions, y nada más. Jekyll se ejecuta en el build si lo usas, y el resultado son archivos. No hay manejador de peticiones, así que mail() de PHP, un script de Node o un formulario CGI en Python no van a funcionar, hagas el commit que hagas.

Puedes ver el fallo desde una terminal. Un POST a cualquier página de un sitio de Pages recibe esto:

Terminal
curl -i -X POST -d "message=hello" https://ada.github.io/notebook/contact.html
Respuesta
HTTP/2 405
server: Varnish
content-length: 131

<html>
<head><title>405 Not Allowed</title></head>

Otras cuatro cosas distinguen a Pages de otros hosts estáticos, y cada una afecta al formulario de contacto:

Rasgo de PagesQué significa para el formulario
Los project sites viven bajo una ruta, username.github.io/repo/Una URL de agradecimiento /thanks.html apunta al user site, no a tu proyecto. Cada URL necesita la parte /repo/
No hay ajuste para cabeceras de respuesta personalizadasUna Content-Security-Policy solo puede venir de una etiqueta <meta> en la página
El repositorio suele ser públicoTodo lo que contiene es público. El id del formulario puede estar ahí; una clave de API secreta o la contraseña de un email, nunca
GitHub dice que los sitios de Pages no deben usarse para transacciones sensibles como enviar contraseñas o números de tarjeta de créditoQue el formulario de contacto sea solo eso. No recojas datos de tarjetas ni contraseñas de cuentas con él
Qué ejecuta GitHub Pages y a dónde tiene que ir el envío de un formulario de contactoqué ejecuta GitHub Pagesa dónde llega el envío del visitantegit pusha la rama de PagesbuildJekyll o un workflowde Actionsarchivosestáticosenusername.github.io/repocontact.htmlservida por PagesIkiformguarda, te avisarepo/thanks.htmlservida por Pages405 Not Allowedno se guarda nadadesplieguePOST303action="/contact"Qué ejecuta GitHub Pages y a dónde tiene que ir el envío de un formulario de contactoqué ejecuta GitHub Pagesa dónde llega el envío del visitantegit pusha la rama de PagesbuildJekyll o unworkflow de Actionsarchivosestáticosenusername.github.io/repocontact.htmlservida por PagesIkiformguarda, te avisarepo/thanks.htmlservida por Pages405 NotAllowedno seguarda nadadesplieguePOST303action="/contact"
  1. Haces push a la rama desde la que publica Pages.
  2. Jekyll o tu workflow de Actions genera el sitio una vez.
  3. Pages sirve el resultado como archivos estáticos en github.io o en tu dominio personalizado.
  4. Un visitante envía el formulario de contacto. Si su action apunta a tu propio sitio, Pages responde 405 Not Allowed.
  5. Con el action apuntando a tu endpoint de Ikiform, Ikiform guarda el mensaje y te envía un email.
  6. Ikiform redirige el navegador a la página de agradecimiento, que Pages sirve como cualquier otro archivo.
El código se ejecuta en GitHub una vez por cada push. Nada tuyo se ejecuta cuando un visitante pulsa Enviar, y por eso el envío tiene que salir de github.io.

El formulario

En un sitio escrito a mano, añade contact.html junto a index.html. Este es para un project site en ada.github.io/notebook, así que la redirección incluye /notebook/:

contact.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com"
    />
    <title>Contact</title>
  </head>
  <body>
    <h1>Get in touch</h1>
    <form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
      <label for="name">Name</label>
      <input id="name" name="name" autocomplete="name" required />

      <label for="email">Email</label>
      <input id="email" name="email" type="email" autocomplete="email" required />

      <label for="about">What is it about?</label>
      <select id="about" name="about">
        <option value="question">A question about the project</option>
        <option value="hire">Work together</option>
        <option value="other">Something else</option>
      </select>

      <label for="message">Message</label>
      <textarea id="message" name="message" rows="6" minlength="10" required></textarea>

      <input type="hidden" name="page" value="docs-home" />
      <input type="hidden" name="_redirect" value="https://ada.github.io/notebook/thanks.html" />
      <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
      <button type="submit">Send</button>
    </form>
  </body>
</html>
  • La Content-Security-Policy en <meta> es opcional. Mantenla si ya tienes una, y asegúrate de que form-action y connect-src incluyen https://ikiform.com. Pages no puede enviar la cabecera por ti.
  • El select about te dice por qué alguien escribió antes de que abras el mensaje. Cambia las opciones por los temas sobre los que la gente te escribe de verdad.
  • El input oculto page registra en qué página estaba el formulario, algo útil cuando el mismo formulario está en varias páginas de documentación.
  • Mantén email como nombre del campo. Los emails de notificación lo usan como dirección de respuesta.

Si el mismo HTML se despliega bajo varias rutas, o no sabes cuál será la ruta final, construye la redirección en el navegador a partir de la dirección de la propia página. Pon esto después del formulario:

contact.html
<script>
  document.querySelectorAll('input[name="_redirect"]').forEach((input) => {
    input.value = new URL("thanks.html", document.baseURI).href;
  });
</script>

¿Usas Jekyll? No escribas la URL a mano. La guía de Jekyll tiene un include que la construye con absolute_url, que añade tu baseurl en los project sites. ¿Generas con Astro, Vite o Hugo mediante un workflow de Actions? Pon la ruta base del framework en /repo/ para que carguen los assets del formulario, y usa la guía de Hugo o la de tu framework para el formulario en sí.

Dominios permitidos en github.io

Añade ada.github.io, con tu propio nombre de usuario. Cubre tu user site y todos tus project sites, porque los project sites comparten el mismo host. Añade también tu dominio personalizado si usas uno; example.com también cubre www.example.com.

Nunca añadas github.io por sí solo. Coincidiría cualquier sitio de Pages, incluidas las copias de tu formulario en sitios de otras personas. Con dominios permitidos configurados, se sigue un _redirect a https://ada.github.io/notebook/thanks.html; sin ellos, Ikiform lo ignora y muestra su página de agradecimiento alojada.

Activa Enforce HTTPS en los ajustes de Pages del repositorio. Ikiform solo sigue valores de _redirect que empiezan por https:, y un dominio personalizado que aún no tiene certificado sirve páginas http:.

Reemplazar un enlace mailto:

Muchos sitios de Pages empiezan con un enlace mailto: en el pie de página. Cambiarlo por el formulario impide que los nuevos scrapers lean tu dirección en la página, y los visitantes sin una app de correo configurada por fin pueden contactarte. Dos cosas que debes saber antes de borrarlo:

  • La dirección sigue en el historial de tu repositorio, y en cualquier fork o archivo de la página antigua. El formulario ayuda a partir de ahora; no vuelve privada una dirección que lleva años siendo pública.
  • Las respuestas salen de tu propia bandeja de entrada. Los emails de notificación ponen al remitente como dirección de respuesta, así que pulsar Reply funciona como el enlace mailto:, pero desde el otro lado.

Ikiform nunca envía emails al visitante, así que indica en la página de agradecimiento cuándo puede esperar una respuesta. Si recibes más mensajes de los que puedes contestar por email, un webhook puede dejar cada uno en Slack o Discord.

Comprueba que funciona

Haz esto en la dirección github.io en producción. Pages no tiene despliegues de preview, así que prueba primero en local con un servidor local.

  1. Añade localhost y username.github.io en Allowed domains, más tu dominio personalizado si tienes uno.
  2. Haz commit de contact.html y thanks.html, espera a que termine el despliegue de Pages en la pestaña Actions del repositorio y abre la página publicada.
  3. Envía el formulario. Deberías llegar a tu propio thanks.html, con la ruta del proyecto en la barra de direcciones.
  4. Abre Submissions en el panel. El nuevo envío está ahí con todos los campos que enviaste.
  5. Revisa la bandeja de entrada de cada destinatario de notificaciones. Cada uno recibe un email con los campos, y Reply va a quien envió el formulario cuando este tiene un campo email.
  6. ¿No llegó nada? Cambia el filtro de estado del panel a Spam. El spam recibe la misma respuesta de éxito que un envío real.

Problemas habituales

ProblemaQué hacer
405 Not Allowed al pulsar EnviarEl formulario desplegado sigue enviando a tu propio sitio. Mira el código fuente de la página en github.io y revisa el action; un build antiguo en caché puede seguir publicado unos minutos.
La página de agradecimiento de Ikiform en lugar de la tuyaAl valor de _redirect le falta la ruta del repositorio, es http:, o el formulario no tiene dominios permitidos. Añade username.github.io y usa la URL https: completa.
403 origin_not_allowed, o todos los envíos acaban en spamEl dominio de la página no está en Allowed domains del formulario. Añádelo, más localhost para las pruebas locales. Los subdominios de un dominio de la lista están permitidos.
Los envíos funcionan en local, pero la consola muestra un error de CSP en github.ioA una Content-Security-Policy en <meta> de tu layout le falta https://ikiform.com en form-action o connect-src.

Preguntas

¿Te atascaste en un paso o quieres una guía para tu caso? Escribe a hello@ikiform.com.