Lista de espera para um novo SaaS

Lista de espera para um novo SaaS7 min de leitura

Como adicionar uma lista de espera a um site no Framer

Duas formas de coletar inscrições na lista de espera no Framer: o formulário do Framer com webhook para o Ikiform, ou um code component que mantém os limites por visitante e as verificações de spam.

Preet SutharFundador, Ikiform

Os formulários do Framer não conseguem enviar para uma URL externa, mas conseguem mandar cada envio para um webhook, então o caminho sem código é o formulário do Framer com o seu endpoint do Ikiform como webhook. Para um lançamento público, use um pequeno code component: o navegador envia diretamente, então o limite de taxa por visitante, os domínios permitidos e o honeypot do Ikiform continuam funcionando.

O Framer é onde muitas páginas de pré-lançamento são construídas, porque a página em si sai numa tarde. O formulário da lista de espera é onde a coisa fica menos óbvia. O bloco de formulário do Framer não consegue mudar o action, então você escolhe entre dois caminhos que se comportam de forma diferente quando o tráfego real chega.

O que muda no Framer

  • Sem action personalizado. Um formulário do Framer envia para os destinos do Framer: o armazenamento dele, e-mail ou um Webhook. Um webhook é o único jeito de chegar a um backend externo sem código.
  • Os webhooks vêm dos servidores do Framer, não do visitante. O Ikiform vê os endereços IP do Framer e nenhum header Origin. Isso desliga três proteções: domínios permitidos, o limite de taxa por visitante e a verificação humana.
  • Novas tentativas. O Framer trata qualquer coisa diferente de um 2xx como falha e tenta de novo até cinco vezes. Ele não segue redirecionamentos. O Ikiform responde a um envio JSON com 201, então uma inscrição armazenada é um sucesso na primeira vez.
  • Code components são React de verdade. Eles rodam no navegador do visitante no site publicado, então um formulário dentro de um deles envia como qualquer outra página web.
Duas formas de uma inscrição numa lista de espera do Framer chegar ao IkiformFormulário do Framer com webhookCode componentFormulário doFramerna sua páginaServidores doFramerJSON, até 5tentativasIkiformvê o IP do Framer,sem origemcode componentformulário React nasua páginanavegador dovisitantefetch com chave deidempotênciaIkiformvê o visitante e o seudomínioenviowebhookenvioenvioDuas formas de uma inscrição numa lista de espera do Framer chegar ao IkiformFormulário do Framer com webhookCode componentFormulário do Framerna sua páginaServidores do FramerJSON, até 5 tentativasIkiformvê o IP do Framer, sem origemcode componentformulário React na sua páginanavegador do visitantefetch com chave de idempotênciaIkiformvê o visitante e o seu domínioenviowebhookenvioenvio
  1. No caminho do webhook, o visitante envia o formulário do Framer e os servidores do Framer encaminham os campos ao Ikiform como JSON.
  2. O Ikiform vê os servidores do Framer como remetente, então não consegue diferenciar os visitantes nem conferir em que site o formulário está.
  3. Com o code component, o navegador do visitante envia direto ao Ikiform.
  4. O Ikiform vê o IP de cada visitante e o seu domínio, então todas as proteções se aplicam.
Os dois caminhos armazenam a mesma inscrição. A diferença é quem o Ikiform acha que a enviou.

Escolha um caminho

ItemFormulário do Framer com webhookCode component
ConfiguraçãoDois campos na barra lateral direitaUm arquivo de código, colado uma vez
EstiloOs estilos e estados de formulário do FramerEstilos inline que você edita, ou suas próprias classes
Limite de taxa por visitanteNão. Todos os envios compartilham os IPs do FramerSim, 5 por minuto cada
Domínios permitidosDeixe vazioDefina com seus domínios do Framer e personalizados
HoneypotNãoSim
Verificação humanaDesligadaDesligada, porque envia com fetch
Rastreamento de origemNenhuma?ref= da URL, ou um padrão por instância

O caminho do webhook serve para uma página que você divulga para algumas centenas de pessoas. Se você está prestes a divulgar a página num lugar grande, use o code component: uma rajada de inscrições de bots pelos servidores do Framer só pode ser barrada pela pontuação de spam.

Caminho 1: o formulário do Framer com webhook

  1. Adicione à página um formulário com um input Email. Nas configurações do input, defina o nome dele como email. O Framer envia o nome de cada input como chave JSON, e o Ikiform usa email para o reply-to e a verificação de domínios descartáveis.
  2. Selecione o formulário. Ao lado de Send To, escolha Add… › Webhook e cole o seu endpoint, https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx. O signing secret é opcional; o Ikiform não confere a assinatura do Framer.
  3. No Ikiform, deixe Allowed domains vazio e a verificação de spam desligada. Com qualquer um dos dois ligado, todo envio do Framer é sinalizado ou recusado, porque não traz origem e não consegue rodar uma verificação de navegador.
  4. Defina o limite de taxa como Relaxed, ou Custom no dia do lançamento. O Standard conta todos os envios do Framer como um único visitante, e o formulário inteiro tem limite de 300 por minuto.
  5. Estilize o estado de sucesso do formulário no Framer. Ele aparece quando o Ikiform responde 201.
Um envio que o Ikiform recusa, como um sem e-mail com uma definição de campos ativa, falha do mesmo jeito nas cinco tentativas do Framer. Confira a entrega no Framer se as inscrições pararem de chegar.

Caminho 2: um code component

No painel Assets, abra Code, crie um arquivo de código chamado WaitlistSignup e cole isto. Arraste-o para a página e defina Form ID no painel de propriedades.

WaitlistSignup.tsx
import { addPropertyControls, ControlType } from "framer";
import { type CSSProperties, type FormEvent, useRef, useState } from "react";

const box: CSSProperties = {
  font: "inherit",
  padding: "12px 14px",
  borderRadius: 10,
  border: "1px solid rgba(0, 0, 0, 0.15)",
};

interface Props {
  formId: string;
  buttonLabel: string;
  joinedText: string;
  source: string;
}

export default function WaitlistSignup({ formId, buttonLabel, joinedText, source }: Props) {
  const [status, setStatus] = useState<"idle" | "sending" | "joined">("idle");
  const [error, setError] = useState("");
  const key = useRef("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const body = new FormData(event.currentTarget);
    const ref = new URLSearchParams(window.location.search).get("ref");
    body.set("ref", (ref || source).slice(0, 40));
    key.current ||= crypto.randomUUID();
    setStatus("sending");
    setError("");
    try {
      const response = await fetch(`https://ikiform.com/f/${formId}`, {
        method: "POST",
        headers: { Accept: "application/json", "Idempotency-Key": key.current },
        body,
      });
      const result = await response.json();
      if (result.ok) return setStatus("joined");
      setError(result.error.fields?.email?.[0] ?? result.error.message);
    } catch {
      setError("Couldn't reach the server. Try again.");
    }
    setStatus("idle");
  }

  if (status === "joined") return <p role="status" style={{ margin: 0 }}>{joinedText}</p>;

  return (
    <form onSubmit={onSubmit} style={{ display: "flex", flexWrap: "wrap", gap: 8, width: "100%" }}>
      <input
        name="email"
        type="email"
        placeholder="you@company.com"
        aria-label="Email"
        autoComplete="email"
        required
        style={{ ...box, flex: "1 1 220px" }}
      />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      <button type="submit" disabled={status === "sending"} style={{ ...box, cursor: "pointer" }}>
        {status === "sending" ? "Joining" : buttonLabel}
      </button>
      {error ? <p role="alert" style={{ flexBasis: "100%", margin: 0 }}>{error}</p> : null}
    </form>
  );
}

addPropertyControls(WaitlistSignup, {
  formId: { type: ControlType.String, title: "Form ID", defaultValue: "frm_xxxxxxxxxxxxxxxx" },
  buttonLabel: { type: ControlType.String, title: "Button", defaultValue: "Join the waitlist" },
  joinedText: { type: ControlType.String, title: "Joined text", defaultValue: "You're on the list." },
  source: { type: ControlType.String, title: "Source", defaultValue: "framer" },
});
  • O painel de propriedades expõe o texto do botão, o texto de inscrito e uma Source padrão. Coloque uma instância no hero com hero e outra no rodapé com footer, e você vai ver qual converte.
  • Um ?ref= na URL da página vence o padrão, então os links de campanha se marcam sozinhos.
  • A chave de idempotência é criada no primeiro envio e reutilizada nas novas tentativas da mesma visita, então um toque duplo no celular armazena uma única inscrição.
  • window só é lido dentro do handler de envio. O Framer renderiza os componentes no servidor primeiro, e mexer em window durante a renderização quebraria isso.

Configurações do Ikiform para o componente

Adicione estes campos na aba Fields do formulário, para o e-mail ser obrigatório e o ref ser permitido:

Campos
{
  "version": 1,
  "title": "Waitlist",
  "fields": [
    { "id": "email", "type": "email", "label": "Email", "required": true },
    { "id": "ref", "type": "text", "label": "Source", "validation": { "max_length": 40 } }
  ]
}
  • Em Allowed domains, adicione your-project.framer.app e o seu domínio personalizado. Nunca adicione framer.app sozinho; todo site do Framer passaria a corresponder.
  • Mantenha a verificação de spam desligada. O componente envia com fetch e não busca um token, então um formulário com a verificação ligada responde 403 challenge_required.
  • Mantenha os limites de taxa Standard. Agora cada visitante é contado separadamente.

Confira se funciona

  1. Publique o site. Webhooks e code components só enviam a partir do site publicado, não do preview do editor.
  2. Envie o formulário no seu domínio framer.app e de novo no seu domínio personalizado.
  3. Abra Submissions no painel. O novo envio está lá com todos os campos que você enviou.
  4. Para o componente, abra a página com ?ref=test e confira se a inscrição mostra ref como test.
  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. Não chegou nada? Mude o filtro de status do painel para Spam. Spam recebe a mesma resposta de sucesso de um envio real.

Pegadinhas

ProblemaO que fazer
Toda inscrição via webhook está no spam com origin_mismatchAllowed domains está definido, e os envios do servidor do Framer não têm origem. Limpe-o para este formulário ou troque para o code component.
O Framer acusa entregas de webhook com falhaO Ikiform recusou o envio: 403 challenge_required com a verificação de spam ligada, ou 422 porque o nome de um input não é email. Corrija a causa; as novas tentativas falham do mesmo jeito.
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.
O componente mostra Unknown field "ref"A definição de campos não lista ref. Adicione-o como na definição acima.
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.

A configuração completa do Framer, incluindo a versão de formulário de contato, está no guia do Framer na documentação. Exportar a lista e enviar o e-mail de lançamento estão no guia principal.

Perguntas

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