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.
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.
- Com a chave na página, todo visitante a baixa, e qualquer um pode chamar a API da ferramenta de e-mail com ela.
- Com um relay, a página só conhece o seu endpoint do Ikiform, que aceita envios de formulário e nada mais.
- O Ikiform filtra spam e envia um webhook assinado ao relay.
- O relay, hospedado fora do site estático, guarda a chave de API e adiciona o assinante.
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 comunknown_field. _redirectprecisa ser uma URLhttps: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.
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
fetchfalhar, por exemplo atrás de um filtro de rede rígido, o script recorre aform.submit(), que envia do jeito tradicional e cai em/subscribed/. - Um
429ganha 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, 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.
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:
Content-Security-Policy: default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.comUm 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
- Adicione seu domínio no ar e
localhostem Allowed domains. - 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
sourceno código-fonte da página. - 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.
- Desligue o JavaScript e inscreva-se pelo rodapé. Você cai em
/subscribed/. - Abra Submissions no painel. As duas inscrições mostram
postefootercomosource. - 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
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.
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.
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.
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.
Travou em algum passo ou quer um guia para o seu caso? Envie um e-mail para hello@ikiform.com.