Receber um formulário no e-mail

Receber um formulário no e-mail6 min de leitura

Uma alternativa ao EmailJS que mantém as chaves fora do cliente

Tire um formulário de contato do EmailJS: por que chaves de e-mail no navegador são um risco, o que muda no seu código e do que você abre mão, como a resposta automática.

Preet SutharFundador, Ikiform

O EmailJS envia e-mail a partir do navegador, então o service id, o template id e a public key vão no JavaScript da sua página para qualquer um copiar. Um backend de formulários não precisa de chave no navegador: o formulário envia para uma URL, e o backend filtra spam, armazena a mensagem e manda um e-mail para você. Você perde a resposta automática do EmailJS para o visitante, que passaria a enviar pela sua própria ferramenta de e-mail.

O que muda aqui

O EmailJS é popular por um bom motivo: ele envia e-mail de verdade a partir de um site estático, sem servidor. Para isso, o navegador chama o EmailJS diretamente com três valores: um service id, um template id e uma public key. Eles estão no seu bundle, legíveis no código-fonte da página ou na aba de rede.

O próprio FAQ do EmailJS diz que quem copia esses valores só consegue enviar seus templates com o seu conteúdo, não e-mails arbitrários. Isso vale para o texto fixo do template. Mas quem chama preenche as variáveis do template, então as chaves copiadas podem enviar seu template com qualquer nome, mensagem e links, tantas vezes quanto a sua cota permitir. E se o To de um template é uma variável, como numa resposta automática para o endereço do visitante, quem chama também escolhe o destinatário.

Quem consegue enviar por uma chave de e-mail no navegador, e por um endpoint de formuláriosdk de e-mail no navegadorendpoint de formuláriosua páginaservice, template epublic keyscript dequalquer umcopia os trêsvaloresseu templatecom o texto dele,para o Tosua páginaum id deformulário, semsegredoIkiformpontuação de spam,limite de taxa,domíniospermitidossó seusdestinatáriosdefinidos no painelver código-fonteenviarenvioe-mailQuem consegue enviar por uma chave de e-mail no navegador, e por um endpoint de formuláriosdk de e-mail no navegadorendpoint de formuláriosua páginaservice, template e public keyscript de qualquer umcopia os três valoresseu templatecom o texto dele, para o Tosua páginaum id de formulário, sem segredoIkiformpontuação de spam, limite detaxa, domínios permitidossó seus destinatáriosdefinidos no painelver código-fonteenviarenvioe-mail
  1. Com um SDK de e-mail no navegador, a página guarda tudo o que é preciso para enviar. Um script em outro lugar pode copiar os ids e a chave e enviar seu template com o próprio texto, até a sua cota.
  2. Se o destinatário do template vem de uma variável, as chaves copiadas também escolhem quem recebe.
  3. Com um endpoint de formulário, a página guarda só um id de formulário. Os envios passam por filtro de spam e limite de taxa, e o e-mail vai só para os destinatários que você define no painel.
Um id de formulário também não é segredo. A diferença é o que um desconhecido consegue fazer com ele: enviar o seu formulário, não mandar o seu e-mail.

O EmailJS tem configurações que reduzem isso, como limitar quais domínios podem usar sua chave e adicionar um captcha a um template. Confira quais o seu plano inclui e ative-as se for ficar. A comparação abaixo é sobre o design, não um veredito sobre o serviço:

ItemEmailJSIkiform
O que o navegador guardaService id, template id e public keyUm id de formulário
Quem decide o destinatárioO template, que pode ler uma variávelSó as configurações do seu painel
Filtro de spamCaptcha por template, se você adicionarHoneypot, pontuação de spam e limites de taxa em todo formulário, mais uma verificação humana opcional
Suas mensagens depoisNa sua caixa de entradaNo painel, na exportação CSV e na API, além da sua caixa de entrada
E-mail para o visitanteSim, com um template de resposta automáticaNão
Seu próprio design de e-mailSim, templates no editor delesNão, um layout fixo com o assunto New submission: <form name>

Se a resposta automática ou um e-mail com a sua marca é o objetivo, o EmailJS, ou o seu próprio servidor com uma API de e-mail, serve melhor. Se você quer a mensagem na sua caixa de entrada, com segurança, e guardada em algum lugar, um backend de formulários serve.

Migre o formulário

Um formulário típico com EmailJS em React, baseado no tutorial de formulário de contato deles:

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>
  );
}

O mesmo formulário enviando ao Ikiform com submit() de @ikiform/sdk:

ContactForm.tsx, depois
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>
  );
}
  • Saem o @emailjs/browser e os três ids. Desinstale o pacote e apague o template ou o service do EmailJS quando o novo formulário estiver funcionando, para que cópias em cache da sua página parem de enviar.
  • user_email vira email. O EmailJS não liga para os nomes, mas o Ikiform usa o campo chamado email como reply-to. Os outros nomes podem ficar, porque as notificações listam os campos como eles chegam.
  • submit() nunca lança exceção. Ele devolve o envelope de erro, então os erros de campo da definição do formulário aparecem abaixo de cada input.
  • O honeypot é novidade. O EmailJS não tinha; este input oculto pega os bots mais simples sem configuração nenhuma.
  • Adicione seu domínio em Allowed domains, para que envios de outros sites sejam marcados como spam.
Usa a verificação humana? Defina o Spam check do formulário como Ikiform, depois chame challenge(FORM_ID) antes do submit e passe o token como turnstileToken. O guia de React mostra como.

Se você usava a resposta automática

O Ikiform não envia e-mail para quem preenche o formulário. Os e-mails de notificação vão só para você e sua equipe, então não há resposta automática, e-mail de confirmação nem double opt-in. Envie esses e-mails pela sua ferramenta de e-mail, disparados por um webhook. Um serviço de envio que você controla a partir de um servidor, como o Resend ou o Postmark, pode enviá-lo a cada webhook submission.created, com o endereço do visitante vindo do payload confiável e não do navegador. O guia de vários destinatários tem um handler que você pode adaptar.

Pergunte-se se você precisa dela. A mensagem de agradecimento na página de um formulário de contato diz ao visitante que deu certo, e uma resposta de verdade sua uma hora depois vale mais que um template.

Confira se funciona

  1. Em Allowed domains do formulário, adicione o domínio do seu site, mais localhost enquanto desenvolve. Deixe vazio para aceitar envios de qualquer lugar.
  2. Procure emailjs e o seu antigo service id no site gerado. Nada deve aparecer.
  3. Preencha o formulário na sua página e envie. Você cai na sua página de agradecimento, ou na hospedada pelo Ikiform.
  4. Confira a caixa de entrada de cada destinatário das notificações. Cada um recebe um e-mail com os campos, e Reply vai para quem enviou quando o formulário tem um campo email.
  5. Envie com o honeypot preenchido, _gotcha=bot. Ele ainda recebe uma resposta de sucesso, mas o envio é armazenado como spam e não dispara e-mail nem webhook.

Se algo der errado

ProblemaO que fazer
O Reply ainda vai para o IkiformO campo ainda é user_email. Renomeie para email, ou para um destes: email, email_address, your_email, reply_to, contact_email.
422 validation_failedO envio não corresponde à definição de campos do formulário. error.fields aponta cada campo e o motivo. Faça o name do input bater com o id do campo.
403 challenge_requiredA verificação de spam do formulário está como Ikiform e um envio por fetch não tinha token. Obtenha um com challenge(formId) de @ikiform/sdk antes de cada envio, ou desligue a verificação.
403 origin_not_allowed, ou todo envio cai no spamO domínio da página não está em Allowed domains do formulário. Adicione-o, mais localhost para testes locais. Subdomínios de um domínio listado são permitidos.

Perguntas

Travou em algum passo ou quer um guia para o seu caso? Envie um e-mail para hello@ikiform.com.