# Como criar uma lista de espera para um novo SaaS

> Monte uma lista de espera pré-lançamento na sua própria landing page: o formulário, proteção contra spam, duplicados, e-mails de confirmação e uma exportação limpa no dia do lançamento.

Publicado em 2026-10-25 · https://ikiform.com/pt-br/guides/waitlist

Coloque um formulário HTML simples na sua landing page que envia o e-mail do visitante a um backend de formulários, para você não precisar rodar um servidor antes do lançamento. Encaminhe cada inscrição por webhook para a ferramenta de e-mail com que você vai lançar, e deixe essa ferramenta enviar qualquer confirmação. No dia do lançamento, exporte a lista como CSV, remova os duplicados por e-mail e envie um único e-mail de lançamento.

Uma lista de espera é o teste mais barato de uma ideia de SaaS. Se desconhecidos deixam o e-mail por algo que ainda não existe, você tem um motivo para construir, e uma lista de pessoas para avisar quando sair. Este guia monta uma na sua própria landing page, sem banco de dados nem servidor, e a deixa pronta para o dia do lançamento.

## Como funciona uma lista de espera

Toda lista de espera tem as mesmas três partes: um formulário na sua página, um lugar que armazena cada inscrição e a ferramenta pela qual você vai mandar e-mail para a lista. Aqui, o formulário envia ao Ikiform. O Ikiform verifica se o envio é spam, armazena, avisa você por e-mail e envia um webhook assinado. Um pequeno relay ou uma ferramenta de automação entrega o endereço à sua ferramenta de e-mail.

Diagrama: Como uma inscrição na lista de espera vai da sua landing page até a sua ferramenta de e-mail

1. Um visitante digita o e-mail na sua landing page, e o formulário envia ao seu endpoint do Ikiform.
2. O Ikiform confere o honeypot, a pontuação de spam e o limite de taxa, e então armazena a inscrição.
3. A inscrição aparece no painel, onde você pode pesquisá-la e exportar a lista como CSV.
4. O Ikiform envia e-mail para você e sua equipe. Ele nunca envia e-mail para quem se inscreveu.
5. O Ikiform envia um webhook submission.created assinado.
6. Um relay ou uma ferramenta de automação adiciona o endereço à sua ferramenta de e-mail, que envia qualquer confirmação e, depois, o e-mail de lançamento.

A ferramenta de e-mail é opcional no primeiro dia. Você pode coletar por semanas e importar um CSV depois; o webhook mantém os dois em sincronia desde o início.

## Quatro formas de rodar uma lista de espera

Não existe uma única ferramenta certa. A tabela compara as quatro configurações mais comuns pelo que fazem bem e pelo que custam depois.

| Abordagem | Bom em | Contras |
| --- | --- | --- |
| SaaS de lista de espera, como LaunchList ou Waitlister | Links de indicação, rankings e posição na fila, prontos para usar. Uma página hospedada se você ainda não tem site. | Sua lista fica em outro produto. O widget tem a cara deles, a menos que você pague ou mude o estilo. O preço geralmente cresce com as inscrições. |
| Formulário incorporado de um criador de formulários, como [Typeform](https://ikiform.com/alternatives/typeform) ou [Tally](https://ikiform.com/alternatives/tally) | Fluxos caprichados de uma pergunta, sem nenhum código. | Um iframe que não combina com a sua página e carrega scripts próprios. Marca, limites de respostas e webhooks dependem do plano. |
| Seu próprio banco de dados e rota de API | Controle total sobre os dados e qualquer lógica, como códigos de indicação. | Você constrói proteção contra spam, limites de taxa, notificações e exportação, e precisa de um servidor mesmo que a landing page seja estática. |
| Backend de formulários, como o Ikiform | Seu próprio HTML em qualquer site. Filtro de spam, armazenamento, alertas por e-mail, webhooks assinados e exportação CSV sem servidor. | Sem ranking de indicações e sem e-mails para quem se inscreve. Isso vem da sua ferramenta de e-mail. |

Escolha um SaaS de lista de espera se a mecânica de indicações é o plano inteiro. Escolha um backend de formulários se você quer que o formulário tenha a cara do resto do seu site, que a lista seja portátil e que os e-mails saiam da ferramenta que você vai usar depois do lançamento de qualquer jeito. O resto deste guia usa o backend de formulários.

## O formulário

Para começar, peça um e-mail e nada mais. Cada campo a mais custa inscrições, e você pode fazer perguntas depois, por e-mail, a quem já disse sim. Escolha sua stack abaixo; a escolha vale para todos os exemplos de código destes guias.

**HTML**

Cole o formulário onde a inscrição deve aparecer. Ele envia do jeito tradicional e redireciona, então funciona sem nenhum JavaScript.

`index.html`:

```html
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <label for="email">Email</label>
  <input id="email" name="email" type="email" placeholder="you@company.com" autocomplete="email" required />
  <input type="hidden" name="ref" value="homepage" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button>Join the waitlist</button>
</form>
```

Para manter os visitantes na página, adicione este script depois do formulário. Ele envia com `fetch`, troca o formulário pela frase de agradecimento e mostra os erros de campo do servidor.

`waitlist.js`:

```js
const form = document.querySelector('form[action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx"]');
const status = document.createElement("p");
status.setAttribute("role", "alert");
form.append(status);

const labels = {
  email: "Email",
};

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  const button = form.querySelector("button");
  button.disabled = true;
  status.textContent = "";
  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.replaceWith("You're on the list. We'll email you at launch.");
      return;
    }
    const fields = Object.entries(result.error.fields ?? {});
    status.textContent = fields.length
      ? fields.map(([id, messages]) => `${labels[id] ?? id}: ${messages[0]}`).join(" ")
      : result.error.message;
  } catch {
    status.textContent = "Couldn't reach the server. Try again.";
  }
  button.disabled = false;
});
```

Quer ir além com HTML? Leia [Como adicionar um formulário de lista de espera em HTML puro a qualquer landing page](https://ikiform.com/pt-br/guides/waitlist/html.md).

**Next.js**

Uma server action encaminha a inscrição com a sua chave secreta, e `useActionState` mostra o resultado sem recarregar a página. O formulário ainda envia se o JavaScript não tiver carregado.

`app/waitlist/actions.ts`:

```ts
"use server";

import { headers } from "next/headers";

export interface JoinState {
  ok: boolean;
  message: string;
  email?: string;
}

export async function joinWaitlist(_previous: JoinState, formData: FormData): Promise<JoinState> {
  const email = String(formData.get("email") ?? "").trim();
  if (!email.includes("@")) return { ok: false, message: "Enter the email we should send your invite to.", email };

  const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
      Origin: (await headers()).get("origin") ?? "",
    },
    body: JSON.stringify({
      email,
      ref: String(formData.get("ref") ?? "homepage"),
      _gotcha: String(formData.get("_gotcha") ?? ""),
    }),
  });
  const result = await response.json().catch(() => null);
  if (result?.ok) return { ok: true, message: "You're on the list. We'll email you on launch day." };
  return {
    ok: false,
    message: result?.error?.fields?.email?.[0] ?? result?.error?.message ?? "Couldn't join the list. Try again.",
    email,
  };
}
```

`app/waitlist/waitlist-form.tsx`:

```tsx
"use client";

import { useActionState } from "react";
import { type JoinState, joinWaitlist } from "./actions";

const initial: JoinState = { ok: false, message: "" };

export function WaitlistForm() {
  const [state, action, pending] = useActionState(joinWaitlist, initial);

  if (state.ok) return <p role="status">{state.message}</p>;

  return (
    <form action={action}>
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required defaultValue={state.email} placeholder="you@company.com" />
      <input type="hidden" name="ref" value="homepage" />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {state.message ? <p role="alert">{state.message}</p> : null}
      <button disabled={pending}>{pending ? "Joining" : "Join the waitlist"}</button>
    </form>
  );
}
```

Quer ir além com Next.js? Leia [Como montar uma lista de espera em Next.js com uma server action](https://ikiform.com/pt-br/guides/waitlist/nextjs.md).

**Astro**

Uma Astro Action com `accept: "form"` valida o e-mail com Zod e o encaminha. O formulário envia sem JavaScript no cliente, então a página precisa ser renderizada sob demanda: adicione um adapter e mantenha `prerender = false`.

`src/actions/index.ts`:

```ts
import { ActionError, defineAction } from "astro:actions";
import { getSecret } from "astro:env/server";
import { z } from "astro/zod";

export const server = {
  joinWaitlist: defineAction({
    accept: "form",
    input: z.object({
      email: z.email(),
      ref: z.string().default("homepage"),
      _gotcha: z.string().nullish(),
    }),
    handler: async (input, context) => {
      const origin = context.request.headers.get("origin");
      const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
        method: "POST",
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
          Authorization: `Bearer ${getSecret("IKIFORM_SECRET_KEY")}`,
          ...(origin ? { Origin: origin } : {}),
        },
        body: JSON.stringify(input),
      });
      const result = await response.json();
      if (!result.ok) {
        throw new ActionError({
          code: "BAD_REQUEST",
          message: result.error.fields?.email?.[0] ?? result.error.message,
        });
      }
      return { joined: true };
    },
  }),
};
```

`src/pages/index.astro`:

```astro
---
import { actions, isInputError } from "astro:actions";

export const prerender = false;

const result = Astro.getActionResult(actions.joinWaitlist);
---

{result?.data?.joined ? (
  <p role="status">You're on the list. We'll email you on launch day.</p>
) : (
  <form method="POST" action={actions.joinWaitlist}>
    <label for="email">Email</label>
    <input id="email" name="email" type="email" required placeholder="you@company.com" />
    <input type="hidden" name="ref" value="homepage" />
    <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    {result?.error && (
      <p role="alert">{isInputError(result.error) ? "Enter a valid email address." : result.error.message}</p>
    )}
    <button>Join the waitlist</button>
  </form>
)}
```

Quer ir além com Astro? Leia [Monte uma lista de espera em Astro](https://ikiform.com/pt-br/guides/waitlist/astro.md).

**TanStack Start**

Uma função POST `createServerFn` recebe o `FormData`, confere em `.validator()` e encaminha a partir do servidor. A rota a chama via `useServerFn`.

`src/routes/index.tsx`:

```tsx
import { createFileRoute } from "@tanstack/react-router";
import { createServerFn, useServerFn } from "@tanstack/react-start";
import { getRequestHeader } from "@tanstack/react-start/server";
import { type FormEvent, useState } from "react";

const joinWaitlist = createServerFn({ method: "POST" })
  .validator((data: unknown) => {
    if (!(data instanceof FormData)) throw new Error("Expected form data");
    const email = String(data.get("email") ?? "").trim();
    if (!email.includes("@")) throw new Error("Enter the email we should send your invite to.");
    return {
      email,
      ref: String(data.get("ref") ?? "homepage"),
      _gotcha: String(data.get("_gotcha") ?? ""),
    };
  })
  .handler(async ({ data }) => {
    const origin = getRequestHeader("origin");
    const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
        ...(origin ? { Origin: origin } : {}),
      },
      body: JSON.stringify(data),
    });
    const result = await response.json();
    if (result.ok) return { ok: true, message: "You're on the list. We'll email you on launch day." };
    return { ok: false, message: String(result.error.fields?.email?.[0] ?? result.error.message) };
  });

export const Route = createFileRoute("/")({ component: Home });

function Home() {
  const join = useServerFn(joinWaitlist);
  const [state, setState] = useState<{ ok: boolean; message: string } | null>(null);
  const [pending, setPending] = useState(false);

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    setPending(true);
    try {
      setState(await join({ data }));
    } catch (error) {
      setState({ ok: false, message: error instanceof Error ? error.message : "Couldn't join the list. Try again." });
    } finally {
      setPending(false);
    }
  }

  if (state?.ok) return <p role="status">{state.message}</p>;

  return (
    <form method="post" onSubmit={onSubmit}>
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required placeholder="you@company.com" />
      <input type="hidden" name="ref" value="homepage" />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {state?.message ? <p role="alert">{state.message}</p> : null}
      <button disabled={pending}>{pending ? "Joining" : "Join the waitlist"}</button>
    </form>
  );
}
```

Quer ir além com TanStack Start? Leia [Monte uma lista de espera em TanStack Start](https://ikiform.com/pt-br/guides/waitlist/tanstack-start.md).

**React Router**

A `action` da rota encaminha o envio, e `actionData` leva o resultado de volta ao componente. `data()` mantém o status code do Ikiform nos erros.

`app/routes/home.tsx`:

```tsx
import { data, Form, useNavigation } from "react-router";
import type { Route } from "./+types/home";

export async function action({ request }: Route.ActionArgs) {
  const formData = await request.formData();
  const email = String(formData.get("email") ?? "").trim();

  const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
      Origin: request.headers.get("origin") ?? "",
    },
    body: JSON.stringify({
      email,
      ref: String(formData.get("ref") ?? "homepage"),
      _gotcha: String(formData.get("_gotcha") ?? ""),
    }),
  });
  const result = await response.json();
  if (result.ok) return { ok: true, message: "You're on the list. We'll email you on launch day." };
  return data(
    { ok: false, message: String(result.error.fields?.email?.[0] ?? result.error.message) },
    { status: response.status },
  );
}

export default function Home({ actionData }: Route.ComponentProps) {
  const navigation = useNavigation();
  const pending = navigation.state === "submitting";

  if (actionData?.ok) return <p role="status">{actionData.message}</p>;

  return (
    <Form method="post">
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required placeholder="you@company.com" />
      <input type="hidden" name="ref" value="homepage" />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {actionData?.message ? <p role="alert">{actionData.message}</p> : null}
      <button disabled={pending}>{pending ? "Joining" : "Join the waitlist"}</button>
    </Form>
  );
}
```

Quer ir além com React Router? Leia [Monte uma lista de espera em React Router](https://ikiform.com/pt-br/guides/waitlist/react-router.md).

**SvelteKit**

Uma form action padrão encaminha o envio e devolve `fail()` com a mensagem nos erros. `use:enhance` envia sem recarregar e mantém o e-mail digitado no input. No SvelteKit 3, declare `IKIFORM_SECRET_KEY` em `src/env.ts` com `defineEnvVars` para que `$app/env/private` consiga lê-la.

`src/routes/+page.server.ts`:

```ts
import { fail } from "@sveltejs/kit";
import { IKIFORM_SECRET_KEY } from "$app/env/private";
import type { Actions } from "./$types";

export const actions = {
  default: async ({ request, fetch, url }) => {
    const data = await request.formData();
    const email = String(data.get("email") ?? "").trim();
    if (!email.includes("@")) return fail(400, { email, message: "Enter the email we should send your invite to." });

    const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        Authorization: `Bearer ${IKIFORM_SECRET_KEY}`,
        Origin: request.headers.get("origin") ?? url.origin,
      },
      body: JSON.stringify({
        email,
        ref: String(data.get("ref") ?? "homepage"),
        _gotcha: String(data.get("_gotcha") ?? ""),
      }),
    });
    const result = await response.json();
    if (!result.ok) {
      return fail(response.status, {
        email,
        message: String(result.error.fields?.email?.[0] ?? result.error.message),
      });
    }
    return { joined: true };
  },
} satisfies Actions;
```

`src/routes/+page.svelte`:

```svelte
<script lang="ts">
  import { enhance } from "$app/forms";
  import type { PageProps } from "./$types";

  let { form }: PageProps = $props();
  let pending = $state(false);
</script>

{#if form?.joined}
  <p role="status">You're on the list. We'll email you on launch day.</p>
{:else}
  <form
    method="POST"
    use:enhance={() => {
      pending = true;
      return async ({ update }) => {
        await update();
        pending = false;
      };
    }}
  >
    <label for="email">Email</label>
    <input id="email" name="email" type="email" required value={form?.email ?? ""} placeholder="you@company.com" />
    <input type="hidden" name="ref" value="homepage" />
    <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    {#if form?.message}
      <p role="alert">{form.message}</p>
    {/if}
    <button disabled={pending}>{pending ? "Joining" : "Join the waitlist"}</button>
  </form>
{/if}
```

Quer ir além com SvelteKit? Leia [Monte uma lista de espera em SvelteKit](https://ikiform.com/pt-br/guides/waitlist/sveltekit.md).

**Nuxt**

Uma server route mantém a chave secreta longe do navegador. Defina `NUXT_IKIFORM_SECRET_KEY` no ambiente, e o Nuxt preenche `runtimeConfig.ikiformSecretKey` com ela.

`server/api/waitlist.post.ts`:

```ts
export default defineEventHandler(async (event) => {
  const body = await readBody<{ email?: string; ref?: string; _gotcha?: string }>(event);
  const config = useRuntimeConfig(event);
  const origin = getRequestHeader(event, "origin");

  const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      Authorization: `Bearer ${config.ikiformSecretKey}`,
      ...(origin ? { Origin: origin } : {}),
    },
    body: JSON.stringify({
      email: body.email?.trim() ?? "",
      ref: body.ref ?? "homepage",
      _gotcha: body._gotcha ?? "",
    }),
  });
  const result = await response.json();
  if (!result.ok) {
    throw createError({
      status: response.status,
      message: String(result.error.fields?.email?.[0] ?? result.error.message),
    });
  }
  return { joined: true };
});
```

`nuxt.config.ts`:

```ts
export default defineNuxtConfig({
  runtimeConfig: {
    ikiformSecretKey: "",
  },
});
```

`app/components/WaitlistForm.vue`:

```vue
<script setup lang="ts">
const email = ref("");
const trap = ref("");
const status = ref<"idle" | "sending" | "joined">("idle");
const error = ref("");

async function join() {
  status.value = "sending";
  error.value = "";
  try {
    await $fetch("/api/waitlist", {
      method: "POST",
      body: { email: email.value, ref: "homepage", _gotcha: trap.value },
    });
    status.value = "joined";
  } catch (failure) {
    error.value = (failure as { data?: { message?: string } }).data?.message ?? "Couldn't join the list. Try again.";
    status.value = "idle";
  }
}
</script>

<template>
  <p v-if="status === 'joined'" role="status">You're on the list. We'll email you on launch day.</p>
  <form v-else @submit.prevent="join">
    <label for="email">Email</label>
    <input id="email" v-model="email" name="email" type="email" required placeholder="you@company.com" />
    <input v-model="trap" name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    <p v-if="error" role="alert">{{ error }}</p>
    <button :disabled="status === 'sending'">{{ status === "sending" ? "Joining" : "Join the waitlist" }}</button>
  </form>
</template>
```

Quer ir além com Nuxt? Leia [Monte uma lista de espera em Nuxt](https://ikiform.com/pt-br/guides/waitlist/nuxt.md).

**React**

Uma single-page app não tem servidor próprio, então o formulário envia direto ao Ikiform a partir do navegador. Nunca coloque uma chave secreta neste código; quem protege o formulário é **Allowed domains**.

`src/WaitlistForm.tsx`:

```tsx
import { type FormEvent, useState } from "react";

const ENDPOINT = "https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx";

export function WaitlistForm() {
  const [status, setStatus] = useState<"idle" | "sending" | "joined">("idle");
  const [error, setError] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const body = new FormData(event.currentTarget);
    setStatus("sending");
    setError("");
    try {
      const response = await fetch(ENDPOINT, { method: "POST", headers: { Accept: "application/json" }, body });
      const result = await response.json();
      if (result.ok) {
        setStatus("joined");
        return;
      }
      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">You're on the list. We'll email you on launch day.</p>;

  return (
    <form action={ENDPOINT} method="post" onSubmit={onSubmit}>
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required placeholder="you@company.com" />
      <input type="hidden" name="ref" value="homepage" />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {error ? <p role="alert">{error}</p> : null}
      <button disabled={status === "sending"}>{status === "sending" ? "Joining" : "Join the waitlist"}</button>
    </form>
  );
}
```

**Vue**

O mesmo envio pelo navegador num single-file component do Vue. O formulário mantém o `action`, então ainda funciona se o script não carregar.

`src/components/WaitlistForm.vue`:

```vue
<script setup lang="ts">
import { ref } from "vue";

const ENDPOINT = "https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx";

const status = ref<"idle" | "sending" | "joined">("idle");
const error = ref("");

async function join(event: SubmitEvent) {
  const body = new FormData(event.target as HTMLFormElement);
  status.value = "sending";
  error.value = "";
  try {
    const response = await fetch(ENDPOINT, { method: "POST", headers: { Accept: "application/json" }, body });
    const result = await response.json();
    if (result.ok) {
      status.value = "joined";
      return;
    }
    error.value = result.error.fields?.email?.[0] ?? result.error.message;
  } catch {
    error.value = "Couldn't reach the server. Try again.";
  }
  status.value = "idle";
}
</script>

<template>
  <p v-if="status === 'joined'" role="status">You're on the list. We'll email you on launch day.</p>
  <form v-else :action="ENDPOINT" method="post" @submit.prevent="join">
    <label for="email">Email</label>
    <input id="email" name="email" type="email" required placeholder="you@company.com" />
    <input type="hidden" name="ref" value="homepage" />
    <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    <p v-if="error" role="alert">{{ error }}</p>
    <button :disabled="status === 'sending'">{{ status === "sending" ? "Joining" : "Join the waitlist" }}</button>
  </form>
</template>
```

- O endpoint é a URL do seu formulário. Troque `frm_xxxxxxxxxxxxxxxx` pelo id que está no painel.
- `name="email"` é o id do campo. Mantenha como `email`: o Ikiform o usa 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, e as versões no servidor o repassam sem mexer.
- O input oculto `ref` registra de onde veio a inscrição. Mude o valor dele por página ou campanha.
- Com uma definição de campos na aba **Fields**, um input que ela não lista é recusado com `422` e um erro `unknown_field`, então liste `ref` além de `email`, como abaixo.
- Um envio de HTML simples redireciona o navegador depois do sucesso. Defina **Redirect after submit** nas configurações do formulário com a sua própria página de agradecimento, ou o Ikiform mostra a página hospedada dele.

`Campos`:

```json
{
  "version": 1,
  "title": "Waitlist",
  "fields": [
    { "id": "email", "type": "email", "label": "Email", "required": true, "placeholder": "you@company.com" },
    { "id": "ref", "type": "text", "label": "Source", "validation": { "max_length": 40 } }
  ]
}
```

> 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](https://ikiform.com/pt-br/docs/rate-limits.md) 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.

As versões no servidor repassam o header `Origin` do navegador, porque o Ikiform identifica os testes gratuitos de `localhost` por ele e o `fetch` do próprio servidor não envia nenhum.

Os guias de frameworks na documentação vão mais a fundo: [Next.js](https://ikiform.com/pt-br/docs/guides/nextjs.md), [Astro](https://ikiform.com/pt-br/docs/guides/astro.md), [SvelteKit](https://ikiform.com/pt-br/docs/guides/sveltekit.md), [Nuxt](https://ikiform.com/pt-br/docs/guides/nuxt.md), [React](https://ikiform.com/pt-br/docs/guides/react.md), [Vue](https://ikiform.com/pt-br/docs/guides/vue.md) e [HTML puro](https://ikiform.com/pt-br/docs/html.md). O [SDK](https://ikiform.com/pt-br/docs/sdk.md) encapsula a requisição do navegador como `submit()`, e o [modelo de formulário de lista de espera](https://ikiform.com/forms/waitlist) gera a versão com quatro campos.

## Campos que valem a pena pedir

O e-mail é o único campo de que você precisa. Se quiser organizar a lista antes do lançamento, adicione no máximo mais dois. Experimente a versão com quatro campos abaixo: ela roda o validador do Ikiform no seu navegador e mostra o que seria armazenado, ou o `422` que você receberia de volta.

Formulário: Waitlist

- **E-mail** (`email`, email, obrigatório)
- **Nome** (`name`, text)
- **O que você faz?** (`role`, select)
- **Como você conheceu a gente?** (`source`, text)
- **Origem** (`ref`, text)

Os campos do [modelo de lista de espera](https://ikiform.com/forms/waitlist) mais o `ref`, que a sua página preenche com um input oculto em vez de uma caixa visível. Cole na aba **Fields** do formulário e o Ikiform aplica tudo no servidor.

| Campo | Por que perguntar | Lembre-se |
| --- | --- | --- |
| `email` | A lista em si | Obrigatório. Use `type="email"` para os celulares mostrarem o teclado certo |
| `name` | Um e-mail de lançamento personalizado | Opcional. O primeiro nome basta |
| `role` | Convidar fundadores primeiro, ou escrever um e-mail diferente para engenheiros | Um `select` mantém as respostas limpas para filtrar |
| `source` | Descobrir qual canal funciona antes de gastar com ele | Texto livre é sincero, mas bagunçado. Um input oculto `ref` é mais limpo, veja abaixo |
| `ref` | Contar as inscrições por página ou campanha sem perguntar | Um input oculto. `text` opcional com `max_length` 40 |

Para acompanhar de onde vêm as inscrições sem perguntar, adicione um input oculto por página ou por campanha. Nomes que começam com `_` são instruções para o Ikiform e nunca são armazenados, então use um nome simples como `ref`:

`Inputs ocultos`:

```html
<input type="hidden" name="ref" value="homepage" />
<input type="hidden" name="_redirect" value="https://example.com/thanks" />
```

Cada envio também registra a origem e o referrer da página, então você vê de qual página veio uma inscrição mesmo sem `ref`. O input `_redirect` manda o navegador para a sua própria página de agradecimento; ele só funciona com uma URL `https:` em um dos **Allowed domains** do formulário.

> Se você vai enviar algo além da novidade do lançamento, como uma newsletter, adicione um checkbox de consentimento obrigatório com a promessa exata como rótulo. O Ikiform armazena `true` com o horário do envio, o que registra quando a pessoa concordou. O [modelo de inscrição em newsletter](https://ikiform.com/forms/newsletter-signup) tem um.

## Mantenha os bots fora da lista

Um formulário público com campo de e-mail atrai bots em poucos dias. Inscrições falsas inflam seus números e, pior, seu e-mail de lançamento cai em spam traps que prejudicam a reputação do remetente. O Ikiform empilha quatro defesas; as duas primeiras já vêm ligadas, sem configuração.

| Defesa | O que ela barra | Configuração |
| --- | --- | --- |
| Honeypot | Bots simples que preenchem todos os inputs | O input oculto `_gotcha` no formulário acima |
| Pontuação de spam | Endereços descartáveis (+2), repetições idênticas (+3), links e frases de spam conhecidas. Com 5, uma inscrição é armazenada como spam | Nenhuma |
| Limites de taxa | Scripts que enviam em loop | Ligado por padrão: Standard, 5 por minuto por visitante, e 300 por minuto para o formulário inteiro |
| Verificação humana | Bots que rodam um navegador de verdade | Defina **Spam check** como **Ikiform**. Ele roda o Cloudflare Turnstile sem chaves para gerenciar |

Spam é armazenado com uma resposta de sucesso normal, então o bot não aprende nada. Ele não dispara e-mail nem webhook e não conta no seu uso. Confira o filtro **Spam** no painel uma vez por semana no primeiro mês; se uma inscrição real cair lá, marque-a como não spam e o webhook dela é enviado na hora.

Adicione também o seu domínio em **Allowed domains**. Envios de outros sites passam a ser marcados como spam, ou recusados com `403 origin_not_allowed` se você escolher **Reject**. Com a verificação humana ligada, o formulário HTML não precisa de mudança. A versão com `fetch` precisa pedir um token antes com `challenge()` de `@ikiform/sdk`; a [documentação de spam](https://ikiform.com/pt-br/docs/spam.md#human-check) mostra as quatro linhas.

> Divulgações de lançamento chegam em rajadas. Se você divulgar a página num lugar grande, o limite de 300 por minuto do formulário inteiro pode bater antes do limite de qualquer visitante. Aumente-o com um limite de taxa Custom na véspera. Um escritório ou uma conferência inteira compartilha um único IP, então o Relaxed, 20 por minuto por visitante, combina com esses dias.

## A mesma pessoa, duas vezes

As pessoas se inscrevem de novo por outra aba, por um segundo dispositivo ou uma semana depois, quando esquecem. O Ikiform armazena todo envio aceito e não mescla nem rejeita repetições pelo e-mail. Veja o que acontece com uma repetição:

| Caso | Resultado |
| --- | --- |
| Mesmo e-mail, campos diferentes ou mais de 24 horas depois | Um segundo envio normal |
| Os mesmos campos de novo em até 24 horas | +3 na pontuação de spam, abaixo de 5, então um segundo envio normal |
| Uma repetição idêntica de um domínio de e-mail descartável | +3 e +2, o que chega a 5: armazenado como spam |

Então espere alguns duplicados e remova-os onde a lista for parar. Três lugares funcionam, do menor para o maior esforço:

1. **Na ferramenta de e-mail.** A maioria das ferramentas de e-mail identifica os contatos pelo endereço, então adicionar o mesmo endereço duas vezes atualiza um contato em vez de criar dois.
2. **Na exportação.** Remova os e-mails duplicados do CSV antes de importá-lo em qualquer lugar. Há duas formas logo abaixo.
3. **No seu relay.** Passe o endereço para minúsculas e tire os espaços, depois insira-o com uma restrição de unicidade ou um upsert, para que uma repetição não faça nada.

Quer evitar que um clique duplo crie duas linhas? Envie uma [chave de idempotência](https://ikiform.com/pt-br/docs/frameworks.md#idempotency) por carregamento de página com a versão `fetch`. Isso impede repetições acidentais; não impede alguém de se inscrever duas vezes de propósito.

## Confirmando as inscrições

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](https://ikiform.com/pt-br/docs/webhooks.md). Isso é proposital: a ferramenta que envia o seu e-mail de lançamento também deve enviar a confirmação, para que descadastros e devoluções fiquem registrados num lugar só.

Duas formas de conectar o Ikiform a ela:

- **Sem código.** Adicione um endpoint de webhook que aponta para uma ferramenta de automação e use a ação dela para a sua ferramenta de e-mail. Veja [Zapier](https://ikiform.com/blog/form-submissions-to-zapier.md), [Make](https://ikiform.com/blog/form-submissions-to-make.md) ou [n8n](https://ikiform.com/blog/form-submissions-to-n8n.md).
- **Um pequeno relay.** Um route handler em qualquer host Node verifica a assinatura e chama a API da sua ferramenta de e-mail. O exemplo abaixo é uma rota do Next.js; o mesmo código roda num Cloudflare Worker.

`app/api/waitlist/route.ts`:

```ts
import { verifyWebhook, WebhookVerificationError } from "@ikiform/sdk/webhooks";

export async function POST(request: Request) {
  const payload = await request.text();
  try {
    const event = await verifyWebhook({
      payload,
      headers: request.headers,
      secret: process.env.IKIFORM_WEBHOOK_SECRET!,
    });
    if (event.type !== "submission.created") return new Response(null, { status: 204 });
    const email = String(event.data.fields.email ?? "").trim().toLowerCase();
    if (!email) return new Response(null, { status: 204 });
    const response = await fetch(process.env.LIST_API_URL!, {
      method: "POST",
      headers: {
        authorization: `Bearer ${process.env.LIST_API_KEY}`,
        "content-type": "application/json",
      },
      body: JSON.stringify({
        email,
        name: event.data.fields.name ?? null,
        tags: ["waitlist", String(event.data.fields.role ?? "unknown")],
      }),
    });
    if (response.status === 409) return new Response(null, { status: 204 });
    if (!response.ok) return new Response(`List answered ${response.status}`, { status: 502 });
    return new Response(null, { status: 204 });
  } catch (error) {
    if (error instanceof WebhookVerificationError) return new Response(error.reason, { status: 400 });
    throw error;
  }
}
```

- `verifyWebhook` confere a assinatura e o timestamp, então ninguém mais consegue adicionar endereços por esta URL.
- Troque o `fetch` pela chamada de adicionar contato da sua ferramenta de e-mail. A maioria recebe um e-mail e uma lista ou tag, e envia o próprio e-mail de confirmação quando o double opt-in está ligado naquela ferramenta.
- Responder `204` diz ao Ikiform que a entrega deu certo. Um `502` faz o Ikiform tentar de novo, com o mesmo `webhook-id`, então uma instabilidade curta na sua ferramenta de e-mail não faz você perder inscrições.

Adicione o endpoint em **Webhooks** no painel e copie o segredo `whsec_` dele para `IKIFORM_WEBHOOK_SECRET`. Um workspace pode ter 3 endpoints. A [documentação de webhooks](https://ikiform.com/pt-br/docs/webhooks.md) explica as novas tentativas e o payload, e [seu próprio servidor](https://ikiform.com/blog/form-submissions-to-your-own-server.md) tem uma versão que armazena cada entrega uma única vez.

## Dia do lançamento

Diagrama: Uma lista de espera da primeira inscrição ao dia do lançamento

1. A página entra no ar com o formulário enviando ao Ikiform.
2. Toda semana, divulgue a página e leia as novas inscrições e as respostas.
3. Uma semana antes do lançamento, exporte a lista como CSV, remova os duplicados e divida por função.
4. No dia do lançamento, envie um único e-mail para a lista pela sua ferramenta de e-mail.

A maior parte do trabalho é a exportação uma semana antes. O e-mail de lançamento em si é curto.

### Exporte a lista

Abra **Submissions** no painel, escolha o formulário da lista de espera, defina o status como **Received** e o período como **All time**, e então escolha **Export CSV**. As colunas são `id`, `created_at`, `form` e `status`, e depois uma coluna por campo. O arquivo é UTF-8 com byte order mark, então os acentos sobrevivem ao Excel. Uma exportação tem até 100.000 linhas; passando disso, pagine pela [API REST](https://ikiform.com/pt-br/docs/guides/export.md#api).

Para remover os duplicados no Google Sheets, importe o CSV e use esta fórmula numa aba vazia, com o e-mail na coluna E:

`Google Sheets`:

```text
=SORTN(A2:F, 9^9, 2, LOWER(TRIM(E2:E)), TRUE)
```

Ela mantém uma linha por endereço, ignorando maiúsculas e espaços, ordenada por e-mail. Ou rode isto com Node na pasta que contém a exportação. Ele mantém a primeira linha de cada endereço e supõe que não há vírgulas dentro dos valores, o que vale para um formulário só com e-mail:

`dedupe.mjs`:

```js
import { readFileSync, writeFileSync } from "node:fs";

const [header = "", ...rows] = readFileSync("submissions.csv", "utf8").replace(/^\uFEFF/, "").trim().split(/\r?\n/);
const columns = header.split(",");
const emailAt = columns.indexOf("email");
const seen = new Set();
const unique = rows.filter((row) => {
  const email = row.split(",")[emailAt]?.trim().toLowerCase();
  if (!email || seen.has(email)) return false;
  seen.add(email);
  return true;
});

writeFileSync("waitlist.csv", [header, ...unique].join("\n"));
console.log(`${rows.length} rows, ${unique.length} unique emails`);
```

### Envie o e-mail de lançamento

Envie pela sua ferramenta de e-mail, não pela sua caixa pessoal. Uma caixa pessoal mandando para centenas de endereços de uma vez parece spam para os provedores de e-mail, e você vai querer o tratamento de descadastros a partir do momento em que enviar. Seja breve:

`E-mail de lançamento`:

```text
Subject: Acme is live. You're in.

Hi Ada,

You joined the Acme waitlist in the spring. Today it's open.

Sign in here: https://acme.example/start

You get the first 30 days free because you waited.
Reply to this email if anything breaks. It comes to me.

Ada, founder of Acme
```

- Diga o que está no ar e coloque o link direto para a página de cadastro ou de login.
- Recompense a espera, com um trial mais longo ou um desconto, e diga isso uma vez só.
- Envie em nome de uma pessoa. As respostas são o melhor feedback inicial que você vai ter.
- Mande primeiras linhas diferentes para fundadores e engenheiros se você coletou `role`. É por isso que vale coletar.

> Se você coletou por meses sem confirmar os endereços, envie primeiro para uma fatia pequena e acompanhe a taxa de devolução. Uma lista cheia de erros de digitação e endereços mortos prejudica o domínio de onde você vai mandar os e-mails do produto.

## Escolha o seu caso

Os passos acima funcionam em qualquer lugar. Estas páginas mostram o que muda para um lançamento ou um criador de sites específico.

- [Lista para lançamento no Product Hunt](https://ikiform.com/pt-br/guides/waitlist/product-hunt.md): Colete uma lista semanas antes do Product Hunt, marque cada inscrição pelo canal e envie o e-mail do dia do lançamento em duas ondas, no ritmo da virada do horário do Pacífico.
- [Inscrição para beta com triagem](https://ikiform.com/pt-br/guides/waitlist/beta-signup.md): Um formulário de candidatura ao beta com perguntas de triagem que o servidor exige, e um script que pontua os candidatos e escolhe cada turma da lista.
- [Lista de espera para app mobile](https://ikiform.com/pt-br/guides/waitlist/mobile-app.md): Uma lista de espera pré-lançamento para um app iOS ou Android que pergunta qual celular a pessoa usa, manda testadores de iPhone ao TestFlight e preenche um teste fechado do Play.
- [Lista de espera com Next.js](https://ikiform.com/pt-br/guides/waitlist/nextjs.md): Uma lista de espera no App Router do Next.js: uma server action que encaminha as inscrições com chave secreta, useActionState, agradecimento otimista e limite por visitante.
- [Lista de espera no Framer](https://ikiform.com/pt-br/guides/waitlist/framer.md): 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.
- [Lista de espera no Webflow](https://ikiform.com/pt-br/guides/waitlist/webflow.md): Aponte um Form Block do Webflow para o Ikiform, marque as inscrições pela origem com um campo oculto e mantenha o estado de sucesso do Webflow com um script curto para todos os formulários.
- [Lista de espera no Carrd](https://ikiform.com/pt-br/guides/waitlist/carrd.md): Colete inscrições na lista de espera numa página do Carrd: um formulário Send to URL no Pro Plus com rastreamento de UTM, ou um formulário HTML incorporado com honeypot no Pro Standard.
- [Lista de espera em HTML puro](https://ikiform.com/pt-br/guides/waitlist/html.md): Uma página de lista de espera completa em HTML puro, sem build, que funciona com o JavaScript desligado, mostra um agradecimento na própria página quando carrega e tem plano B quando precisa.
- [Lista de espera com Astro](https://ikiform.com/pt-br/guides/waitlist/astro.md): Uma lista de espera em Astro numa landing page estática: uma Action encaminha cada inscrição, um script a chama e uma página sob demanda pega os envios sem JavaScript.
- [Lista de espera com TanStack Start](https://ikiform.com/pt-br/guides/waitlist/tanstack-start.md): Uma lista de espera em TanStack Start: uma server route responde tanto a um envio de formulário simples quanto ao fetch, e o validateSearch leva a origem e o resultado na URL.
- [Lista de espera com React Router](https://ikiform.com/pt-br/guides/waitlist/react-router.md): Uma lista de espera em React Router no rodapé de todas as páginas: um fetcher envia para uma única route action, marca cada inscrição com a página e funciona sem JavaScript.
- [Lista de espera com SvelteKit](https://ikiform.com/pt-br/guides/waitlist/sveltekit.md): Uma lista de espera em SvelteKit 3: um hook guarda o primeiro ?ref= num cookie, uma form action nomeada encaminha a inscrição e quem volta vê que já se inscreveu.
- [Lista de espera com Nuxt](https://ikiform.com/pt-br/guides/waitlist/nuxt.md): Uma lista de espera em Nuxt numa landing page pré-renderizada: uma rota Nitro aceita $fetch e envios de formulário simples, valida com Zod e redireciona sem JavaScript.

## Confira se funciona

Siga estes passos antes de divulgar a página. 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](https://ikiform.com/pt-br/pricing).

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. Preencha o formulário na sua página e envie. Você cai na sua página de agradecimento, ou na hospedada pelo Ikiform.
3. Abra **Submissions** no painel. O novo envio está lá com todos os campos que você enviou.
4. 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`.
5. Se você adicionou um webhook, abra o endpoint em **Webhooks**. A entrega mostra `succeeded` com a resposta do seu servidor.
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.
8. Inscreva-se duas vezes com o mesmo endereço e confirme que a sua ferramenta de e-mail continua mostrando um único contato.

## Solução de problemas

| Problema | O que fazer |
| --- | --- |
| `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. |
| `429 rate_limited` | Um 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. |
| `403 challenge_required` | A verificação de spam do formulário está como **Ikiform** e um envio por `fetch` não tinha token. Obtenha um com `challenge(formId)` de `@ikiform/sdk` antes de cada envio, ou desligue a verificação. |
| O envio está no painel, mas nenhum e-mail ou webhook chegou | Ele 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. |
| 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. |
| O mesmo e-mail aparece duas vezes | É o esperado. O Ikiform não remove duplicados por e-mail. Remova as repetições na exportação, ou faça upsert por e-mail no seu relay ou na sua ferramenta de e-mail. |
| As inscrições chegam no Ikiform, mas não na ferramenta de e-mail | Abra o endpoint em **Webhooks** e leia o log de entregas. Um `400` significa que o segredo está errado; um `502` com o status da sua ferramenta significa que a ferramenta recusou o contato. |
| `402 test_limit_reached` | O workspace não tem plano e já usou os 50 testes gratuitos de `localhost` deste mês. Escolha um plano ou use **Send test** no painel. |

## Perguntas

### Preciso de um backend para criar uma página de lista de espera?

Nenhum servidor próprio. O formulário precisa de um lugar para onde enviar, e é isso que um backend de formulários é: aponte o action do formulário para o seu endpoint do Ikiform e ele armazena, filtra e encaminha cada inscrição. Sua landing page pode continuar estática.

### O Ikiform envia um e-mail de confirmação para quem entra na lista de espera?

Não. O Ikiform envia e-mail para você e sua equipe, não para quem se inscreve, então não há e-mail de confirmação nem de double opt-in. Envie cada inscrição por webhook para a sua ferramenta de e-mail e ligue o double opt-in lá.

### Como evito inscrições falsas num formulário de lista de espera?

Mantenha o honeypot oculto _gotcha, deixe o limite de taxa padrão ligado, adicione seu domínio em Allowed domains e defina a verificação de spam como Ikiform para ter uma verificação do Cloudflare Turnstile sem chaves para gerenciar. Endereços descartáveis também somam na pontuação de spam.

### O mesmo e-mail vai ser armazenado duas vezes?

Pode ser. O Ikiform guarda todo envio aceito e não mescla repetições pelo e-mail. Remova os duplicados onde a lista fica: na sua ferramenta de e-mail, numa coluna única do seu banco de dados ou no CSV antes de importá-lo.

### Posso adicionar um programa de indicação ou uma posição na fila?

Não só com o Ikiform. Ele não tem links de indicação nem rankings. Você pode passar um código de indicação num campo oculto e contar você mesmo, ou usar um produto de lista de espera feito em torno de indicações, se isso for central no seu lançamento.

### O Ikiform é gratuito?

Não, não há plano gratuito. Testar é grátis: você pode enviar 50 envios de teste por mês a partir de localhost antes de pagar. Para entrar no ar, o Pro começa em US$ 6/mês para 500 envios, ou você pode pagar uma vez 10.000 envios por US$ 49 ou 50.000 envios por US$ 149, com créditos que nunca expiram.

## Mais guias

- [Lista para lançamento no Product Hunt](https://ikiform.com/pt-br/guides/waitlist/product-hunt.md): Colete uma lista semanas antes do Product Hunt, marque cada inscrição pelo canal e envie o e-mail do dia do lançamento em duas ondas, no ritmo da virada do horário do Pacífico.
- [Inscrição para beta com triagem](https://ikiform.com/pt-br/guides/waitlist/beta-signup.md): Um formulário de candidatura ao beta com perguntas de triagem que o servidor exige, e um script que pontua os candidatos e escolhe cada turma da lista.
- [Lista de espera para app mobile](https://ikiform.com/pt-br/guides/waitlist/mobile-app.md): Uma lista de espera pré-lançamento para um app iOS ou Android que pergunta qual celular a pessoa usa, manda testadores de iPhone ao TestFlight e preenche um teste fechado do Play.
- [Lista de espera com Next.js](https://ikiform.com/pt-br/guides/waitlist/nextjs.md): Uma lista de espera no App Router do Next.js: uma server action que encaminha as inscrições com chave secreta, useActionState, agradecimento otimista e limite por visitante.
- [Lista de espera no Framer](https://ikiform.com/pt-br/guides/waitlist/framer.md): 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.
- [Lista de espera no Webflow](https://ikiform.com/pt-br/guides/waitlist/webflow.md): Aponte um Form Block do Webflow para o Ikiform, marque as inscrições pela origem com um campo oculto e mantenha o estado de sucesso do Webflow com um script curto para todos os formulários.
- [Lista de espera no Carrd](https://ikiform.com/pt-br/guides/waitlist/carrd.md): Colete inscrições na lista de espera numa página do Carrd: um formulário Send to URL no Pro Plus com rastreamento de UTM, ou um formulário HTML incorporado com honeypot no Pro Standard.
- [Lista de espera em HTML puro](https://ikiform.com/pt-br/guides/waitlist/html.md): Uma página de lista de espera completa em HTML puro, sem build, que funciona com o JavaScript desligado, mostra um agradecimento na própria página quando carrega e tem plano B quando precisa.
- [Lista de espera com Astro](https://ikiform.com/pt-br/guides/waitlist/astro.md): Uma lista de espera em Astro numa landing page estática: uma Action encaminha cada inscrição, um script a chama e uma página sob demanda pega os envios sem JavaScript.
- [Lista de espera com TanStack Start](https://ikiform.com/pt-br/guides/waitlist/tanstack-start.md): Uma lista de espera em TanStack Start: uma server route responde tanto a um envio de formulário simples quanto ao fetch, e o validateSearch leva a origem e o resultado na URL.
- [Lista de espera com React Router](https://ikiform.com/pt-br/guides/waitlist/react-router.md): Uma lista de espera em React Router no rodapé de todas as páginas: um fetcher envia para uma única route action, marca cada inscrição com a página e funciona sem JavaScript.
- [Lista de espera com SvelteKit](https://ikiform.com/pt-br/guides/waitlist/sveltekit.md): Uma lista de espera em SvelteKit 3: um hook guarda o primeiro ?ref= num cookie, uma form action nomeada encaminha a inscrição e quem volta vê que já se inscreveu.
- [Lista de espera com Nuxt](https://ikiform.com/pt-br/guides/waitlist/nuxt.md): Uma lista de espera em Nuxt numa landing page pré-renderizada: uma rota Nitro aceita $fetch e envios de formulário simples, valida com Zod e redireciona sem JavaScript.
- [Formulário de contato em site estático](https://ikiform.com/pt-br/guides/contact-form-static-site.md): 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.
- [Receber um formulário no e-mail](https://ikiform.com/pt-br/guides/send-form-to-email.md): Receba todo envio de formulário na caixa de entrada sem servidor de e-mail: por que mailto: e mail() do PHP falham, e uma configuração que filtra spam e permite responder.
- [Acabe com o spam no formulário de contato](https://ikiform.com/pt-br/guides/spam-protection.md): Acabe com o spam no formulário de contato com camadas que não custam nada a pessoas reais: honeypot, tempo, pontuação de conteúdo, limites de taxa e verificação humana só quando precisar.
