Envía un formulario a tu email

Envía un formulario a tu email6 min de lectura

Una alternativa a EmailJS que mantiene las claves fuera del cliente

Saca un formulario de contacto de EmailJS: por qué las claves de email en el navegador son un riesgo, qué cambia en tu código y qué pierdes, como la respuesta automática.

Preet SutharFundador, Ikiform

EmailJS envía emails desde el navegador, así que tu service id, tu template id y tu clave pública van en el JavaScript de tu página para que cualquiera los copie. Un backend de formularios no necesita ninguna clave en el navegador: el formulario envía a una URL y el backend filtra el spam, guarda el mensaje y te manda un email. Pierdes la respuesta automática de EmailJS al visitante, que enviarías desde tu propia herramienta de email.

Qué cambia aquí

EmailJS es popular por una buena razón: envía emails reales desde un sitio estático sin servidor. Para ello, el navegador llama directamente a EmailJS con tres valores: un service id, un template id y una clave pública. Están en tu bundle, legibles en el código fuente de la página o en la pestaña de red.

Las propias preguntas frecuentes de EmailJS dicen que quien los copie solo puede enviar tus plantillas con tu contenido, no emails arbitrarios. Eso vale para el texto fijo de la plantilla. Pero quien llama rellena las variables de la plantilla, así que las claves copiadas pueden enviar tu plantilla con cualquier nombre, mensaje y enlaces, tantas veces como permita tu cuota. Y si el To de una plantilla es una variable, como en una respuesta automática a la dirección del visitante, quien llama también elige el destinatario.

Quién puede enviar con una clave de email del navegador, y con un endpoint de formulariosdk de email en navegadorendpoint de formulariotu páginaservice, template yclave públicael script decualquieracopia los tresvalorestu plantillacon su texto, a suTotu páginaun id deformulario, sinsecretoIkiformspam, límites,dominiospermitidossolo tusdestinatariosconfigurados en elpanelver código fuenteenviarenvíoemailQuién puede enviar con una clave de email del navegador, y con un endpoint de formulariosdk de email en navegadorendpoint de formulariotu páginaservice, template y clavepúblicael script de cualquieracopia los tres valorestu plantillacon su texto, a su Totu páginaun id de formulario, sin secretoIkiformspam, límites, dominiospermitidossolo tus destinatariosconfigurados en el panelver código fuenteenviarenvíoemail
  1. Con un SDK de email en el navegador, la página contiene todo lo necesario para enviar. Un script en otro sitio puede copiar los ids y la clave y enviar tu plantilla con su propio texto, hasta agotar tu cuota.
  2. Si el destinatario de la plantilla sale de una variable, las claves copiadas también eligen quién la recibe.
  3. Con un endpoint de formulario, la página solo contiene un id de formulario. Los envíos se filtran contra el spam y tienen límite de frecuencia, y los emails solo van a los destinatarios que configures en el panel.
Un id de formulario tampoco es un secreto. La diferencia es lo que un desconocido puede hacer con él: enviar tu formulario, no enviar tus emails.

EmailJS tiene ajustes que reducen esto, como limitar qué dominios pueden usar tu clave y añadir un captcha a una plantilla. Comprueba cuáles incluye tu plan y actívalos si te quedas. La comparación de abajo trata del diseño, no es un veredicto sobre el servicio:

ItemEmailJSIkiform
Qué contiene el navegadorService id, template id, clave públicaUn id de formulario
Quién decide el destinatarioLa plantilla, que puede leer una variableSolo los ajustes de tu panel
Filtro antispamCaptcha por plantilla, si lo añadesHoneypot, puntuación de spam y límites de frecuencia en todos los formularios, más una verificación humana opcional
Tus mensajes despuésEn tu bandejaEn el panel, la exportación CSV y la API, además de en tu bandeja
Email al visitanteSí, con una plantilla de respuesta automáticaNo
Tu propio diseño de emailSí, plantillas en su editorNo, un diseño fijo con el asunto New submission: <form name>

Si lo importante es la respuesta automática o un email con tu marca, encaja mejor EmailJS, o tu propio servidor con una API de email. Si quieres el mensaje en tu bandeja, de forma segura y guardado en algún sitio, encaja un backend de formularios.

Migra el formulario

Un formulario típico de EmailJS en React, basado en su tutorial de formulario de contacto:

ContactForm.tsx, antes
import emailjs from "@emailjs/browser";
import { type FormEvent, useRef, useState } from "react";

export function ContactForm() {
  const form = useRef<HTMLFormElement>(null);
  const [status, setStatus] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if (!form.current) return;
    try {
      await emailjs.sendForm("service_abc123", "template_xyz789", form.current, {
        publicKey: "aBcDeFgHiJkLmNoPq",
      });
      setStatus("Message sent.");
    } catch {
      setStatus("Couldn't send your message.");
    }
  }

  return (
    <form ref={form} onSubmit={onSubmit}>
      <input name="user_name" required />
      <input name="user_email" type="email" required />
      <textarea name="message" required />
      <button>Send</button>
      <p role="status">{status}</p>
    </form>
  );
}

El mismo formulario enviando a Ikiform con submit() de @ikiform/sdk:

ContactForm.tsx, después
import { submit } from "@ikiform/sdk";
import { type FormEvent, useState } from "react";

const FORM_ID = "frm_xxxxxxxxxxxxxxxx";

export function ContactForm() {
  const [status, setStatus] = useState<"idle" | "sending" | "sent">("idle");
  const [fields, setFields] = useState<Record<string, string[]>>({});
  const [error, setError] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setStatus("sending");
    setFields({});
    setError("");
    const result = await submit(FORM_ID, new FormData(event.currentTarget));
    if (result.ok) {
      setStatus("sent");
      return;
    }
    setFields(result.error.fields ?? {});
    setError(result.error.code === "network_error" ? "You're offline. Try again." : result.error.message);
    setStatus("idle");
  }

  if (status === "sent") return <p role="status">Message sent. We'll reply by email.</p>;

  return (
    <form onSubmit={onSubmit}>
      <label htmlFor="user_name">Name</label>
      <input id="user_name" name="user_name" required />
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required />
      {fields.email ? <p role="alert">{fields.email[0]}</p> : null}
      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" required />
      {fields.message ? <p role="alert">{fields.message[0]}</p> : null}
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {error && !Object.keys(fields).length ? <p role="alert">{error}</p> : null}
      <button disabled={status === "sending"}>{status === "sending" ? "Sending" : "Send"}</button>
    </form>
  );
}
  • @emailjs/browser y los tres ids desaparecen. Desinstala el paquete y borra la plantilla o el servicio de EmailJS cuando el nuevo formulario funcione, para que las copias en caché de tu página dejen de enviar.
  • user_email pasa a ser email. A EmailJS no le importan los nombres, pero Ikiform usa el campo llamado email como reply-to. Los demás nombres pueden quedarse, porque las notificaciones enumeran los campos tal como llegan.
  • submit() nunca lanza una excepción. Devuelve el objeto de error, así los errores de campo de la definición del formulario aparecen debajo de su input.
  • El honeypot es nuevo. EmailJS no tenía; este input oculto atrapa a los bots más simples sin configurar nada.
  • Añade tu dominio en Allowed domains, para que los envíos desde otros sitios se marquen como spam.
¿Usas la verificación humana? Pon el Spam check del formulario en Ikiform, llama a challenge(FORM_ID) antes de submit y pasa el token como turnstileToken. La guía de React lo muestra.

Si usabas la respuesta automática

Ikiform no envía emails a la persona que rellena el formulario. Los emails de notificación van solo a ti y a tu equipo, así que no hay respuesta automática, email de confirmación ni doble opt-in. Envíalos desde tu herramienta de email, activada por un webhook. Un servicio de envío que controles desde un servidor, como Resend o Postmark, puede enviarlo con cada webhook submission.created, tomando la dirección del visitante del payload de confianza en lugar del navegador. La guía de varios destinatarios tiene un handler que puedes adaptar.

Pregúntate si lo necesitas. El mensaje de agradecimiento en la página de un formulario de contacto le dice al visitante que funcionó, y una respuesta real tuya una hora después vale más que una plantilla.

Comprueba que funciona

  1. En Allowed domains del formulario, añade el dominio de tu sitio, más localhost mientras desarrollas. Déjalo vacío para aceptar envíos desde cualquier lugar.
  2. Busca emailjs y tu antiguo service id en tu sitio compilado. No debería aparecer nada.
  3. Rellena el formulario en tu página y envíalo. Llegas a tu página de agradecimiento, o a la alojada de Ikiform.
  4. Revisa la bandeja de cada destinatario de las notificaciones. Cada uno recibe un email con los campos, y Reply va a quien envió el formulario cuando este tiene un campo email.
  5. 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 ningún email ni webhook.

Si algo sale mal

ProblemaQué hacer
Reply sigue yendo a IkiformEl campo sigue siendo user_email. Renómbralo a email, o a uno de email, email_address, your_email, reply_to, contact_email.
422 validation_failedEl envío no coincide con la definición de campos del formulario. error.fields indica cada campo y el motivo. Haz que el name del input coincida con el id del campo.
403 challenge_requiredLa verificación antispam del formulario está en Ikiform y un envío con fetch no llevaba token. Consigue uno con challenge(formId) de @ikiform/sdk antes de cada envío, o desactiva la verificación.
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.

Preguntas

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