Guias

Guias16 min de leitura

Adicione um formulário de contato a um site estático sem backend

Coloque um formulário de contato funcionando num site estático sem servidor: para onde vai o POST, spam, respostas por e-mail e código para todo host e framework.

Preet SutharFundador, Ikiform

Um host estático serve arquivos e recusa um POST de formulário, geralmente com 405 ou 404, então o formulário precisa enviar para outro lugar. Aponte o action do formulário para um backend de formulários como o Ikiform, que armazena cada mensagem, filtra spam e manda um e-mail para você com o remetente como endereço de reply-to. Seu site continua estático: sem código de servidor, sem chaves secretas na página e sem mudança na forma como você faz deploy.

Um site estático é uma pasta de HTML, CSS e JavaScript que um host entrega como arquivos. É por isso que ele é rápido, barato e difícil de quebrar. E também é por isso que o formulário de contato é a primeira coisa que para de funcionar: um envio de formulário é uma requisição que alguém precisa receber, e um host estático não tem nada escutando.

Por que o formulário falha num host estático

Um <form method="post"> sem action envia para a própria página em que está. Num host estático, isso é um arquivo, e arquivos só respondem a GET e HEAD. Enviamos um POST para uma página estática publicada em cada um dos principais hosts em 2026-10-25. Nenhum deles armazenou nada:

HostO que um POST para uma página estática recebe
GitHub Pages405 Not Allowed, como uma pequena página HTML
Vercel405 com corpo vazio
Netlify404, com a página 404 do site se houver uma, a menos que o Netlify Forms tenha detectado aquele formulário no deploy
Cloudflare Pages405 com corpo vazio
Endpoint de site do Amazon S3405 Method Not Allowed, código MethodNotAllowed

A solução é a mesma em todo lugar: enviar o post para uma URL que o aceite. Essa URL pode ser um código que você escreve e hospeda, um recurso do seu host ou um backend de formulários. O resto deste guia usa um backend de formulários, porque ele mantém o site estático.

Para onde vai o envio de um formulário de contato, antes e depois da correçãoaction aponta para o seu siteaction aponta para o Ikiformpágina de contatoHTML estáticohost estáticoserve arquivos,responde GET405 ou 404mensagem perdidapágina de contatomesmo HTML, novoactionIkiformverifica, armazena,envia e-mailpágina deagradecimentode volta ao seu sitePOSTPOST303Para onde vai o envio de um formulário de contato, antes e depois da correçãoaction aponta para o seu siteaction aponta para o Ikiformpágina de contatoHTML estáticohost estáticoserve arquivos, responde GET405 ou 404mensagem perdidapágina de contatomesmo HTML, novo actionIkiformverifica, armazena, envia e-mailpágina de agradecimentode volta ao seu sitePOSTPOST303
  1. Sem action, ou com um action que aponta para um caminho no seu próprio site, o navegador envia para o host estático.
  2. O host não tem código para rodar num POST, então responde 405 ou 404 e a mensagem se perde.
  3. Com o action apontando para o seu endpoint do Ikiform, o envio vai para um servidor que o aceita.
  4. O Ikiform verifica se a mensagem é spam, armazena e manda um e-mail para você.
  5. O Ikiform responde com um redirecionamento 303, e o navegador carrega a página de agradecimento no seu próprio site.
Só o action do formulário muda. A página, o host e o deploy continuam como estão.

O que acontece com cada mensagem

O Ikiform é o endpoint para o qual o formulário envia. Ele não hospeda sua página nem renderiza seu formulário; os dois continuam com você. Para cada envio, ele roda as verificações de spam, armazena a mensagem e a distribui para os lugares onde você lê.

O que o Ikiform faz com uma mensagem do formulário de contatoformulário decontatoem qualquer host estáticoIkiformverifica spam, depoisarmazenae-mail denotificaçãoReply vai ao remetentepainelbusca, filtro de spam,CSVwebhook assinadoSlack, Discord, seucódigoenvioO que o Ikiform faz com uma mensagem do formulário de contatoformuláriode contatoem qualquerhost estáticoIkiformverifica spam,depoisarmazenae-mail denotificaçãoReply vai aoremetentepainelbusca, filtro despam, CSVwebhookassinadoSlack, Discord, seucódigoenvio
  1. Um visitante preenche o formulário de contato, e ele é enviado ao seu endpoint do Ikiform.
  2. O Ikiform confere o honeypot, o limite de taxa e a pontuação de spam, e então armazena a mensagem.
  3. Cada destinatário das notificações recebe um e-mail, com o reply-to definido como o endereço do remetente.
  4. A mensagem aparece no painel, onde você pode pesquisá-la, recuperar spam e exportar CSV.
  5. Se você adicionar um webhook, o Ikiform envia uma cópia assinada para o Slack, o Discord, uma ferramenta de automação ou o seu próprio código.
O e-mail basta para a maioria dos formulários de contato. Adicione um webhook quando as mensagens tiverem que chegar onde você já trabalha.

Cinco formas de receber o formulário de um site estático

AbordagemBom emContras
Um link mailto: ou action="mailto:..."Zero configuração. Serve para uma página pessoal com pouco tráfego.Abre o app de e-mail do visitante, se houver um configurado. Nada é armazenado nem filtrado, e seu endereço fica no HTML para os scrapers.
O recurso de formulários do seu host, como o Netlify FormsUm atributo, nenhuma outra conta, e geralmente incluído no plano de hospedagem.Só funciona naquele host. Mudar de host significa refazer o formulário, e formulários renderizados por JavaScript precisam de gambiarras.
Uma função serverless escrita por vocêControle total sobre a validação, o armazenamento e o e-mail que você envia.Agora você é dono de um backend: uma chave de API de e-mail, filtro de spam, limites de taxa, novas tentativas e logs. O formato de função de cada host é diferente.
Um formulário hospedado incorporado, como Google Forms, Tally ou TypeformSem código. Bom para formulários longos ou com várias etapas.Um iframe ou um link para outro site, com a aparência e os scripts deles na sua página.
Um backend de formulários, como o Ikiform, o Formspree, o Basin ou o Web3FormsSeu próprio HTML em qualquer host. Armazenamento, filtro de spam e e-mail sem servidor.Mais uma conta e mais uma fatura, e os dados do formulário ficam com esse serviço.

Se você está na Netlify e nunca pretende sair, o Netlify Forms é imbatível em esforço. Se você quer escrever o backend, uma função é um bom projeto de fim de semana. Se você quer que o formulário continue funcionando quando trocar de host ou de framework, use um backend de formulários. As comparações colocam os backends lado a lado.

O formulário

Todos os exemplos abaixo enviam a partir do navegador, porque um deploy estático não tem servidor de onde enviar. Escolha sua stack; a escolha vale para todos os exemplos de código destes guias. Geradores de sites estáticos têm páginas de configuração próprias: Jekyll, Hugo, Eleventy e Gatsby.

Esta é a integração inteira para um site escrito à mão. O formulário envia do jeito tradicional, então funciona com o JavaScript desligado, e o navegador cai no seu thanks.html depois de um envio bem-sucedido.

contact.html
<form id="contact" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <label for="name">Name</label>
  <input id="name" name="name" autocomplete="name" maxlength="120" required />
  <p class="field-error" data-error-for="name" hidden></p>

  <label for="email">Email</label>
  <input id="email" name="email" type="email" autocomplete="email" required />
  <p class="field-error" data-error-for="email" hidden></p>

  <label for="message">Message</label>
  <textarea id="message" name="message" rows="6" minlength="10" maxlength="5000" required></textarea>
  <p class="field-error" data-error-for="message" hidden></p>

  <input type="hidden" name="_redirect" value="https://example.com/thanks.html" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button type="submit">Send message</button>
  <p id="contact-status" role="status"></p>
</form>
<p id="contact-sent" role="status" hidden>Thanks. Your message is on its way, and I'll reply by email.</p>

<script src="/contact.js" defer></script>

O script é opcional. Ele mantém o visitante na página, coloca cada erro de campo abaixo do input correspondente e deixa a mensagem digitada no lugar quando algo falha.

contact.js
const form = document.querySelector("#contact");
const status = document.querySelector("#contact-status");
const done = document.querySelector("#contact-sent");
const button = form.querySelector("button");

function showErrors(fields = {}) {
  for (const slot of form.querySelectorAll("[data-error-for]")) {
    const name = slot.dataset.errorFor;
    const message = fields[name]?.[0] ?? "";
    slot.textContent = message;
    slot.hidden = !message;
    form.elements[name]?.setAttribute("aria-invalid", message ? "true" : "false");
  }
}

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  button.disabled = true;
  status.textContent = "Sending";
  try {
    const response = await fetch(form.action, {
      method: "POST",
      headers: { Accept: "application/json" },
      body: new FormData(form),
    });
    const result = await response.json();
    if (result.ok) {
      form.hidden = true;
      done.hidden = false;
      return;
    }
    showErrors(result.error.fields);
    status.textContent = result.error.message;
  } catch {
    status.textContent = "Couldn't send. Check your connection and try again.";
  }
  button.disabled = false;
});
  • Troque frm_xxxxxxxxxxxxxxxx pelo id do seu formulário, que está no painel. O id é público por design: ele está no seu HTML assim que você faz o deploy. Nunca coloque uma chave de API secreta num site estático.
  • Mantenha o nome do campo de e-mail como email. O Ikiform o define como endereço de reply-to dos e-mails de notificação e o confere contra domínios de e-mail descartáveis.
  • O input oculto _gotcha é o honeypot. As pessoas nunca o veem; bots que preenchem todos os inputs se marcam como spam.
  • As versões em JavaScript enviam Accept: application/json, então o Ikiform responde 201 em caso de sucesso, ou 422 com um objeto error.fields que aponta cada campo e o problema.

Se o seu framework roda num servidor em produção, você pode enviar a partir de lá e manter o endpoint fora da página. Os guias de Next.js, SvelteKit e Nuxt mostram as versões no servidor, e A partir de um servidor explica as chaves secretas.

As versões no servidor enviam com uma chave secreta, então o Ikiform pula Allowed domains e a verificação humana. Todo envio passa a vir do IP do seu servidor, e o limite Standard de 5 por minuto por visitante conta todos os seus visitantes juntos. Escolha o limite de taxa Relaxed ou Custom para um formulário que você envia por um servidor, e mantenha IKIFORM_SECRET_KEY em variáveis de ambiente só do servidor.

Campos para um formulário de contato

Nome, e-mail e mensagem cobrem quase todo formulário de contato. Um select de assunto vale a pena quando pessoas diferentes respondem tipos diferentes de mensagem. Experimente o formulário abaixo: ele roda o validador do Ikiform no seu navegador e mostra o que seria armazenado, ou o 422 que você receberia de volta.

Prévia

Roda o validador do Ikiform no seu navegador. Nada é enviado.

A mesma definição do modelo de formulário de contato. Cole-a na aba Fields do formulário e o Ikiform a aplica no servidor, diga o HTML o que disser.
CampoPor quêLembre-se
nameComo você cumprimenta a pessoa na respostaUm link num campo de nome é um forte sinal de spam, então mantenha o id name
emailPara onde vai a respostaObrigatório. type="email" abre o teclado certo no celular
topicMande vendas e suporte para a pessoa certaUm select só aceita os valores listados, então é seguro filtrar por ele
messageA razão de ser do formulárioUm tamanho mínimo de 10 impede envios de uma palavra só

Deixe de fora telefone, tamanho da empresa e qualquer outra coisa que você não vá usar na primeira resposta. Cada campo que você adiciona custa mensagens. Sem uma definição de campos, o Ikiform armazena quaisquer inputs com nome que o formulário enviar; com uma, ele rejeita envios que não correspondem.

Upload de arquivos ainda não é suportado. Um envio com arquivo recebe 422 files_not_supported.

Responda pela sua caixa de entrada

Os e-mails de notificação vão para até 10 destinatários confirmados por formulário. Quando o formulário tem um campo email, o reply-to do e-mail é o remetente, então o Reply no seu app de e-mail responde direto para a pessoa. Você nunca precisa copiar um endereço do painel.

As notificações têm limite de 50 e-mails por formulário por hora, e depois um resumo por hora, para que uma onda de spam não soterre sua caixa de entrada. Mensagens marcadas como spam não enviam e-mail nenhum. _subject não muda o assunto dos e-mails de notificação. Ele é repassado aos webhooks e à API como meta.subject.

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.

Quer as mensagens num chat da equipe em vez do e-mail, ou além dele? Adicione um webhook e siga o guia do Slack, do Discord ou do Telegram.

O que o visitante vê depois de enviar

Um envio de HTML simples termina num redirecionamento. O Ikiform responde a um envio bem-sucedido pelo navegador com 303 See Other, e o navegador carrega o destino com um GET, que o seu host estático serve sem problemas.

As três requisições por trás de um envio de formulário HTML simplesnavegadorvisitante clica emEnviarikiform.com/f/…armazena amensagemseu/thanks.htmlum arquivoestáticoPOST303, depois GETAs três requisições por trás de um envio de formulário HTML simplesnavegadorvisitante clica em Enviarikiform.com/f/…armazena a mensagemseu /thanks.htmlum arquivo estáticoPOST303, depois GET
  1. O navegador envia os campos do formulário ao seu endpoint do Ikiform.
  2. O Ikiform armazena a mensagem e responde 303 See Other com um header Location.
  3. O navegador segue o redirecionamento com um GET para a sua página de agradecimento, que o host estático serve como qualquer outro arquivo.
Sem um destino de redirecionamento, o passo 3 carrega a página de agradecimento hospedada pelo Ikiform em vez da sua.

O Ikiform escolhe o destino nesta ordem:

  • Redirect after submit nas configurações do formulário. Qualquer URL http: ou https: funciona, porque só a sua equipe pode defini-la. É a escolha mais simples para um único site.
  • Um campo _redirect no formulário, se for uma URL https: em um dos Allowed domains do formulário. Use-o quando um formulário atende vários sites ou idiomas.
  • Caso contrário, a página de agradecimento hospedada pelo Ikiform, que leva de volta ao seu site quando o envio veio de uma página https: permitida.

Faça da página de agradecimento um arquivo de verdade e mantenha-a fora dos resultados de busca. Diga quando você vai responder; isso poupa um segundo e-mail da mesma pessoa.

thanks.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="robots" content="noindex" />
    <title>Message sent</title>
  </head>
  <body>
    <h1>Thanks, your message is on its way</h1>
    <p>I read every message and reply within two working days, from the address on this site.</p>
    <p><a href="/">Back to the home page</a></p>
  </body>
</html>

As versões em JavaScript acima pulam o redirecionamento. Um envio com Accept: application/json recebe JSON de volta, e a própria página mostra a frase de sucesso.

Spam num formulário estático

Seu endpoint está no código-fonte da página, e bots leem código-fonte. Espere spam poucos dias depois de entrar no ar. O Ikiform empilha as defesas para você poder começar sem configurar nada:

DefesaO que ela barraConfiguração
HoneypotBots que preenchem todos os inputsO input oculto _gotcha
Pontuação de spamLinks no nome (+3), muitos links (+2), frases de spam conhecidas (+2) e endereços descartáveis. Com 5, uma mensagem é armazenada como spamNenhuma
Limites de taxaScripts enviando em loopLigado por padrão: Standard, 5 por minuto por visitante
Domínios permitidosCópias do seu formulário enviando de outros sitesAdicione seus domínios nas configurações do formulário
Verificação humanaBots que controlam um navegador de verdadeDefina Spam check como Ikiform para usar o Cloudflare Turnstile sem chaves

Spam recebe a mesma resposta de sucesso de uma mensagem real, então o bot não aprende nada, e não dispara e-mail nem webhook. Um formulário de contato raramente precisa de mais de um envio da mesma pessoa em poucos minutos, então o Strict, 2 a cada 10 minutos por visitante, combina com ele quando estiver no ar. A documentação de spam explica cada camada, e a verificação humana precisa de uma chamada extra nas versões em JavaScript: challenge() do SDK.

Domínios permitidos e deploys de preview

Allowed domains corresponde a um domínio e aos seus subdomínios. Hosts estáticos dão mais domínios do que você imagina: o domínio de produção, o subdomínio do próprio host e um endereço de preview por branch ou pull request. Alguns previews são subdomínios do endereço do seu site no host e já correspondem; outros não, e precisam de uma entrada própria.

HostAdicionePreviews
GitHub Pagesusername.github.io e seu domínio personalizadoSem previews por padrão
Vercelyour-project.vercel.app e seu domínioURLs de branch não correspondem; adicione o host exato enquanto testa
Netlifyyour-site.netlify.app e seu domíniodeploy-preview-12--your-site.netlify.app não corresponde
Cloudflare Pagesyour-project.pages.dev e seu domínioabc123.your-project.pages.dev já corresponde

Nunca adicione um host compartilhado sozinho, como github.io, vercel.app ou netlify.app. Todo site naquele host passaria a corresponder, inclusive os de outras pessoas.

Se o seu site envia uma Content-Security-Policy

Muitos templates estáticos vêm com uma Content-Security-Policy rígida. Duas diretivas decidem se o formulário funciona: form-action para o envio de HTML simples e connect-src para as versões com fetch. Deixe uma delas de fora e o navegador bloqueia a requisição antes de ela sair da página, com só uma mensagem no console como pista.

Header de resposta
Content-Security-Policy: default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com

Mantenha 'self' em form-action: alguns navegadores também conferem o redirecionamento de volta para a sua página de agradecimento com ela. Onde o header fica depende do host: _headers na Netlify e no Cloudflare Pages, vercel.json na Vercel, uma tag <meta> no GitHub Pages e uma response headers policy do CloudFront na frente do S3. As páginas de cada host abaixo mostram como.

Teste antes do deploy

Abra a página por um servidor local, não clicando duas vezes no arquivo. Uma página aberta como file:// envia Origin: null, que não corresponde a nenhum domínio permitido e não conta como teste local. Qualquer um destes serve a pasta atual:

Terminal
npx serve .

python3 -m http.server 8000

Adicione localhost em Allowed domains enquanto testa. Envios de localhost contam como testes: um workspace sem plano tem 50 por mês, então você pode construir o formulário inteiro antes de pagar.

Escolha o seu caso

O formulário acima funciona em todo host estático. Cada host tem o próprio jeito de falhar, e os próprios domínios de preview e arquivos de headers. Estas páginas tratam deles.

Confira se funciona

Faça isto uma vez no seu domínio publicado e uma vez num preview, se o seu host gera previews. O Ikiform não tem plano gratuito. Testar é grátis: um workspace sem plano aceita 50 envios de teste por mês a partir de localhost. Para entrar no ar, escolha o Pro a partir de US$ 6/mês para 500 envios, ou pague uma vez: 10.000 envios por US$ 49 ou 50.000 envios por US$ 149, com créditos que nunca expiram. Veja os preços.

  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. Envie a partir do seu servidor local e confirme que a mensagem aparece em Submissions.
  3. Faça o deploy e envie a partir do site publicado com o JavaScript desligado. Você deve cair na sua própria página de agradecimento.
  4. Ligue o JavaScript de novo e envie com a mensagem vazia. O erro deve aparecer na página, não como uma página de erro hospedada.
  5. 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.
  6. 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.
  7. Não chegou nada? Mude o filtro de status do painel para Spam. Spam recebe a mesma resposta de sucesso de um envio real.

Solução de problemas

ProblemaO que fazer
405 Method Not Allowed ou 404 logo depois de clicar em EnviarO formulário enviou para o seu próprio site. Confira o atributo action no HTML publicado, não no seu código-fonte; uma etapa de build ou um componente <Form> do framework pode tê-lo substituído.
Nada acontece, e o console menciona Content-Security-PolicyAdicione https://ikiform.com a form-action para envios simples e a connect-src para fetch.
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.
Testes locais são recusados com 402 plan_requiredA página foi aberta como uma URL file://, então o envio não tinha uma origem utilizável e contou como envio real. Sirva a pasta com um servidor local e teste a partir de localhost.
O navegador cai na página de agradecimento do Ikiform em vez da suaO _redirect é ignorado, a menos que o domínio dele esteja em Allowed domains e ele seja uma URL https:. Adicione o domínio, ou defina Redirect after submit nas configurações do formulário.
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.
O envio está no painel, mas nenhum e-mail ou webhook chegouEle foi armazenado como spam. Spam não envia nada. Abra-o no filtro Spam para ver os motivos e marque-o como não spam para enviar o e-mail e os webhooks dele.
429 rate_limitedUm visitante enviou mais do que o limite de taxa do formulário permite. O Standard permite 5 por minuto por visitante. Espere o Retry-After, ou escolha Relaxed ou Custom para formulários que enviam a partir de um único IP compartilhado.

Perguntas

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