Formulário de inscrição em newsletter

Formulário de inscrição em newsletter6 min de leitura

Formulário de inscrição por e-mail num site estático, sem servidor

Adicione um formulário de newsletter a um site estático em Hugo, Jekyll, Eleventy ou Astro, mantenha a chave de API da ferramenta de e-mail fora da página e confirme na ferramenta.

Preet SutharFundador, Ikiform

Num site estático, o formulário envia do navegador do leitor direto ao Ikiform, porque não há servidor seu para receber o envio. Mantenha a chave de API da sua ferramenta de e-mail totalmente fora do site: o navegador só conhece o seu endpoint público do Ikiform, e um relay hospedado em outro lugar adiciona o assinante. Escreva o formulário uma vez como partial ou include, passe um source diferente por posição e adicione um pequeno script para a versão sem recarregar.

O que muda aqui

Um site estático são arquivos num host: GitHub Pages, Netlify, Cloudflare Pages, um bucket S3. Nada que você escreve roda num servidor quando um leitor envia um formulário. Isso descarta o atalho comum de chamar a API da sua ferramenta de e-mail a partir da página, porque tudo que o navegador roda, qualquer um pode ler.

Chaves de API de ferramentas de e-mail raramente se limitam a adicionar um assinante. Uma chave que adiciona um assinante geralmente também consegue ler, exportar ou apagar a lista inteira. Coloque-a no JavaScript de um site estático e ela vai para todo visitante, e dali para qualquer um que abra as ferramentas de desenvolvedor.

Onde fica a chave de API da ferramenta de e-mail num site estáticochave na páginachave num relaypáginaestáticachave de APInoJavaScriptAPI daferramentaqualquer umchamasua listalegível porqualquer umpáginaestáticasó o endpointpúblicoIkiformcheca spam,webhookassinadorelayguarda achave de APIferramentade e-mailadiciona oassinantechaveenvioassinadochaveOnde fica a chave de API da ferramenta de e-mail num site estáticochave na páginachave num relaypágina estáticachave de API no JavaScriptAPI da ferramentaqualquer um chamasua listalegível por qualquer umpágina estáticasó o endpoint públicoIkiformcheca spam, webhook assinadorelayguarda a chave de APIferramenta de e-mailadiciona o assinantechaveenvioassinadochave
  1. Com a chave na página, todo visitante a baixa, e qualquer um pode chamar a API da ferramenta de e-mail com ela.
  2. Com um relay, a página só conhece o seu endpoint do Ikiform, que aceita envios de formulário e nada mais.
  3. O Ikiform filtra spam e envia um webhook assinado ao relay.
  4. O relay, hospedado fora do site estático, guarda a chave de API e adiciona o assinante.
O endpoint do formulário pode ser publicado sem problema. A chave de API, nunca.

Escreva o formulário uma vez

Um formulário de newsletter aparece em vários templates: o rodapé, o fim de cada post, uma página de newsletter. Transforme-o num partial que recebe um valor source, para que cada posição se registre. O markup é idêntico em cada gerador; só a sintaxe de template para source muda.

<form data-newsletter action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <input name="email" type="email" required autocomplete="email" placeholder="you@example.com" aria-label="Email" />
  <label><input name="consent" type="checkbox" value="true" required /> Email me the newsletter. I can unsubscribe at any time.</label>
  <input type="hidden" name="source" value="{{ .source | default "footer" }}" />
  <input type="hidden" name="_redirect" value="https://example.com/subscribed/" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button>Subscribe</button>
  <p role="status" hidden></p>
</form>

Depois coloque-o num template com o nome da posição:

{{ partial "newsletter.html" (dict "source" "post") }}
  • No Astro, use <Newsletter source="post" />. Um build estático do Astro não roda Actions, então o componente envia direto ao Ikiform como os outros.
  • Adicione source à definição de campos do formulário no Ikiform, como texto com um tamanho máximo curto. Com os campos definidos, um input que a definição não lista é recusado com unknown_field.
  • _redirect precisa ser uma URL https: num dos Allowed domains do formulário. Crie a página: /subscribed/ com uma frase pedindo aos leitores que confiram a caixa de entrada.

Um script para todos os formulários da página

Uma página de post pode ter dois formulários de newsletter, um depois do artigo e outro no rodapé. Este script cuida de todo formulário marcado com data-newsletter, e quando um dá certo ele substitui todos, para ninguém ser convidado a se inscrever de novo alguns centímetros abaixo. Carregue-o uma vez no seu layout base.

static/newsletter.js
for (const form of document.querySelectorAll("form[data-newsletter]")) {
  const status = form.querySelector('[role="status"]');
  const button = form.querySelector("button");

  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    button.disabled = true;
    status.hidden = false;
    status.textContent = "Subscribing";
    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) {
        for (const other of document.querySelectorAll("form[data-newsletter]")) {
          other.replaceChildren(Object.assign(document.createElement("p"), {
            textContent: "Almost done. Check your inbox for a confirmation link.",
          }));
        }
        return;
      }
      if (response.status === 429 && response.headers.has("Retry-After")) {
        status.textContent = "Too many tries from this network. Wait a minute and try again.";
      } else {
        const errors = result.error.fields ?? {};
        status.textContent = errors.email?.[0] ?? errors.consent?.[0] ?? result.error.message;
      }
    } catch {
      form.submit();
      return;
    }
    button.disabled = false;
  });
}
  • Se o fetch falhar, por exemplo atrás de um filtro de rede rígido, o script recorre a form.submit(), que envia do jeito tradicional e cai em /subscribed/.
  • Um 429 ganha mensagem própria. Leitores na mesma rede de escritório ou campus compartilham um IP e podem bater juntos no limite por visitante.
  • O formulário funciona com o JavaScript desligado. O script só muda como a resposta é mostrada.

Leitores com o JavaScript desligado caem em /subscribed/, então faça essa página cumprir o papel que o script cumpre para todos os outros: dizer que um e-mail de confirmação está a caminho, dizer o nome do remetente a procurar, mencionar a pasta de spam e linkar de volta para o blog. É uma página comum do seu gerador, construída como qualquer outra, e também é para onde você pode apontar o link de confirmação da ferramenta de e-mail depois.

Onde o relay mora

O relay precisa de uma URL HTTPS pública que rode código. Seu host estático pode ou não oferecer isso:

Host estáticoOpções de relay
GitHub Pages, S3Sem funções. Use um Cloudflare Worker num domínio personalizado, ou uma ferramenta de automação
NetlifyUma Netlify Function no domínio do seu site, ou qualquer uma das opções acima
Cloudflare PagesUm Worker separado num domínio personalizado. O Ikiform recusa URLs pages.dev e workers.dev
VercelUma função no seu domínio. A saída estática pode ficar ao lado de uma rota de API

O código do relay para cada ferramenta de e-mail está nas páginas de Kit, MailerLite e beehiiv. Sem código nenhum, aponte o webhook para Zapier ou n8n e use a ação da ferramenta de e-mail deles. A documentação de webhooks lista as regras de URL.

Sites estáticos costumam ter deploy previews num domínio separado. Envios de um domínio que não está em Allowed domains são marcados como spam ou recusados, então adicione o domínio de preview enquanto testa, ou teste no domínio no ar e em localhost.

Se o seu site envia uma Content Security Policy

Muitos hosts estáticos deixam você definir headers por um arquivo, como _headers no Netlify e no Cloudflare Pages. Se o seu envia uma Content-Security-Policy, o navegador bloqueia o formulário a menos que a política permita o Ikiform. form-action cobre o envio tradicional e connect-src cobre o fetch do script:

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

Um envio bloqueado aparece como erro de CSP no console do navegador e nunca chega ao Ikiform, então nada aparece no painel também.

Confira se funciona

  1. Adicione seu domínio no ar e localhost em Allowed domains.
  2. Faça o build do site e abra uma página de post. Os dois formulários estão lá, cada um com seu próprio valor source no código-fonte da página.
  3. Inscreva-se pelo formulário do post com um endereço seu. Os dois formulários mudam para a frase pedindo para conferir a caixa de entrada.
  4. Desligue o JavaScript e inscreva-se pelo rodapé. Você cai em /subscribed/.
  5. Abra Submissions no painel. As duas inscrições mostram post e footer como source.
  6. Procure a chave de API da sua ferramenta de e-mail no site gerado. Ela não está lá.

Pegadinhas

ProblemaO que fazer
Nada no painel, e um erro de CSP no consoleAdicione https://ikiform.com a form-action e connect-src na sua Content-Security-Policy.
422 com unknown_field para sourceAdicione source à definição de campos do formulário, ou remova o input oculto.
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.
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.

Perguntas

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