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

Publicado em 2026-10-25 · https://ikiform.com/pt-br/guides/newsletter-signup/static-site

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.

Diagrama: Onde fica a chave de API da ferramenta de e-mail num site estático

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.

**Hugo** (`layouts/partials/newsletter.html`)

```html
<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>
```

**Jekyll** (`_includes/newsletter.html`)

```html
<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="{{ include.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>
```

**Eleventy** (`_includes/newsletter.njk`)

```html
<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 or "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>
```

**Astro** (`src/components/Newsletter.astro`)

```astro
---
interface Props {
  source?: string;
}
const { source = "footer" } = Astro.props;
---

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

**Hugo** (`layouts/_default/single.html`)

```html
{{ partial "newsletter.html" (dict "source" "post") }}
```

**Jekyll** (`_layouts/post.html`)

```html
{% include newsletter.html source="post" %}
```

**Eleventy** (`_includes/post.njk`)

```html
{% set source = "post" %}
{% include "newsletter.njk" %}
```

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

```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ático | Opções de relay |
| --- | --- |
| GitHub Pages, S3 | Sem funções. Use um Cloudflare Worker num domínio personalizado, ou uma ferramenta de automação |
| Netlify | Uma Netlify Function no domínio do seu site, ou qualquer uma das opções acima |
| Cloudflare Pages | Um Worker separado num domínio personalizado. O Ikiform recusa URLs `pages.dev` e `workers.dev` |
| Vercel | Uma 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](https://ikiform.com/pt-br/guides/newsletter-signup/kit.md), [MailerLite](https://ikiform.com/pt-br/guides/newsletter-signup/mailerlite.md) e [beehiiv](https://ikiform.com/pt-br/guides/newsletter-signup/beehiiv.md). Sem código nenhum, aponte o webhook para [Zapier](https://ikiform.com/blog/form-submissions-to-zapier.md) ou [n8n](https://ikiform.com/blog/form-submissions-to-n8n.md) e use a ação da ferramenta de e-mail deles. A [documentação de webhooks](https://ikiform.com/pt-br/docs/webhooks.md) 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`:

```text
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

| Problema | O que fazer |
| --- | --- |
| Nada no painel, e um erro de CSP no console | Adicione `https://ikiform.com` a `form-action` e `connect-src` na sua Content-Security-Policy. |
| `422` com `unknown_field` para `source` | Adicione `source` à definição de campos do formulário, ou remova o input oculto. |
| `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. |
| O navegador cai na página de agradecimento do Ikiform em vez da sua | O `_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

### Posso adicionar um formulário de newsletter a um site estático sem backend?

Sim. O formulário envia do navegador a um backend de formulários como o Ikiform, que armazena e filtra cada inscrição. Um relay ou uma ferramenta de automação hospedada em outro lugar a repassa para a sua ferramenta de e-mail, então o site estático em si não roda código de servidor.

### Posso chamar a API da minha ferramenta de e-mail direto da página estática?

Não faça isso. Qualquer chave no JavaScript da página fica visível para todo visitante, e chaves de ferramentas de e-mail geralmente conseguem ler ou alterar a lista inteira. Guarde a chave num relay que rode fora do site.

### Isso funciona no GitHub Pages?

Sim. O formulário é HTML simples e envia ao Ikiform. O GitHub Pages não roda o relay, então hospede-o num Cloudflare Worker com domínio personalizado, ou use uma ferramenta de automação como Zapier ou n8n.

### Quem envia o e-mail de confirmação?

Sua ferramenta de e-mail. O Ikiform nunca envia e-mail para quem se inscreve. O relay adiciona o leitor como não confirmado e a ferramenta envia o próprio link de confirmação.

## Mais guias

- [Formulário de inscrição em newsletter](https://ikiform.com/pt-br/guides/newsletter-signup.md): Crie um formulário de newsletter no seu próprio markup, registre o consentimento, barre inscrições de bots e passe cada assinante para Kit, MailerLite, beehiiv ou qualquer ferramenta.
- [Double opt-in](https://ikiform.com/pt-br/guides/newsletter-signup/double-opt-in.md): O Ikiform não envia e-mails de confirmação, então o double opt-in roda na sua ferramenta de e-mail: um relay adiciona cada inscrição como não confirmada e a ferramenta envia o link.
- [Kit (ConvertKit)](https://ikiform.com/pt-br/guides/newsletter-signup/kit.md): Conecte seu próprio formulário de newsletter ao Kit com um relay que cria o assinante, o adiciona a um formulário do Kit para o incentive email e marca a posição com uma tag.
- [beehiiv](https://ikiform.com/pt-br/guides/newsletter-signup/beehiiv.md): Conecte seu próprio formulário de inscrição ao beehiiv: um relay cria cada subscription com double opt-in ligado, a posição como UTM source e a página como referrer.
- [MailerLite](https://ikiform.com/pt-br/guides/newsletter-signup/mailerlite.md): Conecte seu próprio formulário de inscrição ao MailerLite: um relay adiciona novos leitores como não confirmados para o double opt-in e nunca pede a um assinante que confirme duas vezes.
- [Formulário de newsletter no Next.js](https://ikiform.com/pt-br/guides/newsletter-signup/nextjs.md): Um formulário de newsletter no rodapé do Next.js: uma server action com checagem de consentimento e Turnstile, enviando ao Ikiform, e um route handler que adiciona leitores ao Kit.
