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.
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.
- 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.
- Se o destinatário do template vem de uma variável, as chaves copiadas também escolhem quem recebe.
- 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.
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:
| Item | EmailJS | Ikiform |
|---|---|---|
| O que o navegador guarda | Service id, template id e public key | Um id de formulário |
| Quem decide o destinatário | O template, que pode ler uma variável | Só as configurações do seu painel |
| Filtro de spam | Captcha por template, se você adicionar | Honeypot, pontuação de spam e limites de taxa em todo formulário, mais uma verificação humana opcional |
| Suas mensagens depois | Na sua caixa de entrada | No painel, na exportação CSV e na API, além da sua caixa de entrada |
| E-mail para o visitante | Sim, com um template de resposta automática | Não |
| Seu próprio design de e-mail | Sim, templates no editor deles | Nã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:
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:
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/browsere 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_emailviraemail. O EmailJS não liga para os nomes, mas o Ikiform usa o campo chamadoemailcomo 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.
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
- Em Allowed domains do formulário, adicione o domínio do seu site, mais
localhostenquanto desenvolve. Deixe vazio para aceitar envios de qualquer lugar. - Procure
emailjse o seu antigo service id no site gerado. Nada deve aparecer. - Preencha o formulário na sua página e envie. Você cai na sua página de agradecimento, ou na hospedada pelo Ikiform.
- 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. - 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
| Problema | O que fazer |
|---|---|
| O Reply ainda vai para o Ikiform | O campo ainda é user_email. Renomeie para email, ou para um destes: email, email_address, your_email, reply_to, contact_email. |
422 validation_failed | O 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_required | A 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 spam | O 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
Depende do volume e do plano, e os dois mudam com o tempo, então compare as páginas de preços atuais. O Ikiform não tem plano gratuito; ele cobra por envio, com o Pro ou com pacotes de pagamento único.
O EmailJS diz que uma chave copiada só consegue enviar seus templates, não e-mails personalizados. Mas quem chama ainda preenche as variáveis do template, e se o destinatário for uma variável, escolhe ele também. Restrinja a chave aos seus domínios e adicione um captcha se continuar usando.
Não. Um formulário no navegador envia para a URL do seu formulário sem chave. Chaves secretas existem só para enviar a partir do seu próprio servidor, e o lugar delas é em variáveis de ambiente do servidor.
Não. O Ikiform envia e-mail para você e sua equipe, não para quem enviou, então não há resposta automática nem e-mail de confirmação. Envie um webhook para sua ferramenta de e-mail e mande a confirmação de lá.
Sim, exceto o campo de e-mail: chame-o de email para que as respostas às notificações vão para o visitante. O Ikiform armazena quaisquer nomes de campo que você enviar.
Travou em algum passo ou quer um guia para o seu caso? Envie um e-mail para hello@ikiform.com.