# 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.

Publicado em 2026-10-25 · https://ikiform.com/pt-br/guides/send-form-to-email/emailjs-alternative

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.

Diagrama: Quem consegue enviar por uma chave de e-mail no navegador, e por um endpoint de formulário

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:

|   | 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:

`ContactForm.tsx, antes`:

```tsx
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`:

```tsx
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](https://ikiform.com/pt-br/docs/guides/react.md) 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](https://ikiform.com/pt-br/docs/webhooks.md). 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](https://ikiform.com/pt-br/guides/send-form-to-email/multiple-recipients.md#route) 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

| 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

### O Ikiform é mais barato que o EmailJS?

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.

### É seguro expor a public key do EmailJS?

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.

### O Ikiform precisa de uma chave de API no navegador?

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.

### O Ikiform pode enviar e-mail para quem preencheu o formulário?

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á.

### Posso manter os nomes de campo do EmailJS?

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.

## Mais guias

- [Receber um formulário no e-mail](https://ikiform.com/pt-br/guides/send-form-to-email.md): Receba todo envio de formulário na caixa de entrada sem servidor de e-mail: por que mailto: e mail() do PHP falham, e uma configuração que filtra spam e permite responder.
- [Next.js](https://ikiform.com/pt-br/guides/send-form-to-email/nextjs.md): Duas formas de mandar um formulário Next.js por e-mail com uma Server Action: enviar você mesmo com Resend ou Nodemailer, ou encaminhar a um backend de formulários, comparadas com honestidade.
- [Responder pela caixa de entrada](https://ikiform.com/pt-br/guides/send-form-to-email/reply-to.md): Faça o Reply de uma notificação de formulário responder a quem preencheu: quais nomes de campo funcionam, quando usar _replyto e por que as respostas se perdem.
- [Sem PHP](https://ikiform.com/pt-br/guides/send-form-to-email/without-php.md): Substitua um script contact.php que chama mail() por um formulário que envia a um endpoint hospedado, mantendo seus nomes de campo, sua página de agradecimento e as respostas.
- [Para o Gmail](https://ikiform.com/pt-br/guides/send-form-to-email/gmail.md): Receba os envios de formulário no Gmail sem links mailto:, senhas de app ou Apps Script, e depois rotule, tire do spam e responda pelo Gmail.
- [Vários destinatários](https://ikiform.com/pt-br/guides/send-form-to-email/multiple-recipients.md): Mande um formulário por e-mail para a equipe toda, ou encaminhe cada mensagem à pessoa certa pelo assunto, sem deixar o próprio formulário escolher quem recebe.
