# Envie os envios de formulários HTML para o seu e-mail

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

Publicado em 2026-10-25 · https://ikiform.com/pt-br/guides/send-form-to-email

Um formulário HTML não consegue enviar e-mail sozinho, porque o navegador não tem como falar com um servidor de e-mail. Aponte o `action` do formulário para um backend de formulários, que recebe o envio, filtra spam, armazena e manda um e-mail para você e sua equipe. Dê ao campo de e-mail o nome `email` e o **Reply** na sua caixa de entrada responde direto para quem preencheu o formulário.

A maioria dos sites precisa de uma coisa de um formulário de contato: a mensagem numa caixa de entrada que alguém lê. Isso acaba sendo mais difícil do que parece. O HTML tem o elemento `<form>`, mas nenhum jeito de enviar e-mail, e os atalhos antigos ou devolvem o trabalho para o visitante ou caem no spam. Este guia explica por quê, compara as opções com honestidade e configura um formulário que manda e-mail para você em qualquer site e qualquer stack.

## Por que um formulário não consegue enviar e-mail sozinho

Quando um visitante clica em **Send**, o navegador faz uma requisição HTTP para a URL no `action` do formulário. É tudo o que um formulário consegue fazer. Enviar e-mail exige uma conversa SMTP com um servidor de e-mail, a partir de uma máquina em que o servidor de destino confia. O navegador não consegue abrir essa conexão e, mesmo que conseguisse, um e-mail saindo do notebook de um visitante qualquer seria rejeitado. Então sempre existe algo num servidor entre o formulário e a sua caixa de entrada: seu próprio código, um script no seu host ou um serviço.

Com um backend de formulários, esse algo é uma URL. O formulário envia para `https://ikiform.com/f/<id>` e o Ikiform faz o resto:

Diagrama: Como um envio de formulário vira um e-mail na sua caixa de entrada

1. O visitante envia o formulário, e o navegador ou o seu servidor o envia ao seu endpoint do Ikiform.
2. O Ikiform confere os campos com a definição do formulário, dá uma pontuação de spam ao envio e o armazena.
3. Um envio que atinge a pontuação de spam é armazenado como spam. Ele recebe a mesma resposta de sucesso, mas não dispara e-mail nem webhook.
4. Todos os outros envios mandam um e-mail para cada destinatário, com o endereço do visitante como reply-to.
5. O e-mail chega na sua caixa de entrada segundos depois. Clicar em Reply responde ao visitante.

O visitante nunca vê um endereço de e-mail e nunca abre um app de e-mail. A única coisa na sua página é um formulário.

## Seis formas de levar um formulário até a sua caixa de entrada

Cada uma delas funciona para alguém. A tabela mostra o que cada uma custa para você quando visitantes de verdade, e bots de verdade, começam a usar o formulário.

| Abordagem | Bom em | Contras |
| --- | --- | --- |
| `action="mailto:you@example.com"` | Sem servidor, sem conta, um atributo. | Nada é enviado. O navegador abre o app de e-mail do visitante com um rascunho, ou não faz nada se não houver app de e-mail configurado. Seu endereço fica na página para os scrapers. Veja [o guia do Gmail](https://ikiform.com/pt-br/guides/send-form-to-email/gmail.md). |
| Um script PHP com `mail()` | Roda em quase qualquer hospedagem PHP compartilhada, sem terceiros. | Depende da configuração de e-mail do host, e e-mails que dizem vir do seu domínio muitas vezes falham no SPF ou no DMARC e caem no spam. Sem filtro de spam, sem armazenamento, e nada de PHP em hosts estáticos. Veja [sem PHP](https://ikiform.com/pt-br/guides/send-form-to-email/without-php.md). |
| Um SDK de e-mail no navegador, como o EmailJS | Sem servidor. Modelos de e-mail que você desenha no painel deles. | As chaves e os ids dos modelos ficam no JavaScript da sua página, então qualquer um pode enviar seus modelos com o próprio texto. Veja [a alternativa ao EmailJS](https://ikiform.com/pt-br/guides/send-form-to-email/emailjs-alternative.md). |
| Sua própria rota com uma API de e-mail, como Resend, Postmark, Amazon SES ou Nodemailer via SMTP | Controle total: seu assunto, seu HTML, uma confirmação para o visitante. | Você constrói validação, filtro de spam, limites de taxa, armazenamento e novas tentativas, verifica um domínio de envio e precisa de um servidor mesmo num site estático. Veja [o guia do Next.js](https://ikiform.com/pt-br/guides/send-form-to-email/nextjs.md). |
| Google Apps Script como web app | Roda numa conta Google que você já tem. | As cotas de script limitam para quantos destinatários ele consegue mandar e-mail por dia, a URL não tem proteção contra spam e a manutenção do script fica com você. |
| Um backend de formulários, como o Ikiform, o [Formspree](https://ikiform.com/alternatives/formspree) ou o [Web3Forms](https://ikiform.com/alternatives/web3forms) | Uma URL no `action`. Filtro de spam, armazenamento e e-mail sem servidor. | O layout e o assunto do e-mail são os do serviço. Com o Ikiform, nada nunca é enviado por e-mail ao visitante. |

Se você precisa de um e-mail totalmente com a sua marca, ou de uma confirmação para o visitante, escreva sua própria rota com uma API de e-mail. Se você quer a mensagem na sua caixa de entrada e um jeito de responder, com o spam do lado de fora, um backend de formulários é o que exige menos código. O resto deste guia usa o Ikiform.

## O formulário

Três campos cobrem a maioria dos formulários de contato: quem é a pessoa, para onde responder e o que ela quer. Escolha sua stack abaixo; a escolha vale para todos os exemplos de código destes guias.

**HTML**

Este formulário funciona sem nenhum JavaScript: o navegador o envia, o Ikiform manda um e-mail para você e o visitante cai na página definida em `_redirect`. Cada campo tem um espaço vazio para erro logo abaixo, usado pelo script a seguir.

`contact.html`:

```html
<form id="contact" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="POST">
  <label for="name">Name</label>
  <input id="name" name="name" autocomplete="name" required maxlength="120" />
  <p data-error="name"></p>

  <label for="email">Your email</label>
  <input id="email" name="email" type="email" autocomplete="email" required />
  <p data-error="email"></p>

  <label for="message">Message</label>
  <textarea id="message" name="message" rows="6" required minlength="10"></textarea>
  <p data-error="message"></p>

  <input type="hidden" name="page" value="/contact" />
  <input type="hidden" name="_redirect" value="https://example.com/contact/sent" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button>Send message</button>
  <p id="contact-status" role="status"></p>
</form>
```

O script mantém o visitante na página. Ele envia com `fetch`, escreve cada erro do servidor abaixo do campo correspondente e troca o formulário por uma frase que repete o endereço para o qual você vai responder, para que um erro de digitação fique fácil de notar.

`contact.js`:

```js
const form = document.getElementById("contact");
const status = document.getElementById("contact-status");
const button = form.querySelector("button");

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  for (const slot of form.querySelectorAll("[data-error]")) slot.textContent = "";
  button.disabled = true;
  status.textContent = "Sending";

  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) {
      const email = form.elements.namedItem("email").value;
      const done = document.createElement("p");
      done.setAttribute("role", "status");
      done.textContent = `Sent. We'll reply to ${email}.`;
      form.replaceWith(done);
      return;
    }
    for (const [field, messages] of Object.entries(result.error.fields ?? {})) {
      const slot = form.querySelector(`[data-error="${field}"]`);
      if (slot) slot.textContent = messages[0];
    }
    status.textContent = result.error.fields ? "Check the fields above." : result.error.message;
  } catch {
    status.textContent = "Couldn't send. Check your connection and try again.";
  }
  button.disabled = false;
});
```

**Next.js**

Uma server action envia a mensagem com a sua chave secreta. Ela converte o `error.fields` do Ikiform numa mensagem por campo e devolve os valores digitados, para que nada do que o visitante escreveu se perca num erro.

`app/contact/actions.ts`:

```ts
"use server";

import { headers } from "next/headers";

export interface ContactState {
  sent: boolean;
  email?: string;
  errors: Record<string, string>;
  values?: { name: string; email: string; message: string };
}

export async function sendMessage(_previous: ContactState, formData: FormData): Promise<ContactState> {
  const values = {
    name: String(formData.get("name") ?? "").trim(),
    email: String(formData.get("email") ?? "").trim(),
    message: String(formData.get("message") ?? "").trim(),
  };
  const origin = (await 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 ${process.env.IKIFORM_SECRET_KEY}`,
      ...(origin ? { Origin: origin } : {}),
    },
    body: JSON.stringify({ ...values, page: "/contact", _gotcha: String(formData.get("_gotcha") ?? "") }),
  });
  const result = await response.json().catch(() => null);
  if (result?.ok) return { sent: true, email: values.email, errors: {} };

  const errors: Record<string, string> = {};
  for (const [field, messages] of Object.entries(result?.error?.fields ?? {})) {
    errors[field] = (messages as string[])[0] ?? "Check this field.";
  }
  if (!Object.keys(errors).length) errors.form = result?.error?.message ?? "Couldn't send your message. Try again.";
  return { sent: false, errors, values };
}
```

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

```tsx
"use client";

import { useActionState } from "react";
import { type ContactState, sendMessage } from "./actions";

const initial: ContactState = { sent: false, errors: {} };

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

  if (state.sent) return <p role="status">Sent. We'll reply to {state.email}.</p>;

  return (
    <form action={action}>
      <label htmlFor="name">Name</label>
      <input id="name" name="name" autoComplete="name" required defaultValue={state.values?.name} />
      {state.errors.name ? <p role="alert">{state.errors.name}</p> : null}

      <label htmlFor="email">Your email</label>
      <input id="email" name="email" type="email" autoComplete="email" required defaultValue={state.values?.email} />
      {state.errors.email ? <p role="alert">{state.errors.email}</p> : null}

      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" rows={6} required minLength={10} defaultValue={state.values?.message} />
      {state.errors.message ? <p role="alert">{state.errors.message}</p> : null}

      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {state.errors.form ? <p role="alert">{state.errors.form}</p> : null}
      <button disabled={pending}>{pending ? "Sending" : "Send message"}</button>
    </form>
  );
}
```

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. Cada exemplo de servidor repassa o header `Origin` do navegador quando existe, porque o `fetch` do próprio servidor não envia nenhum e o Ikiform usa esse header para contar os envios de teste gratuitos de `localhost`.

Quer ir além com Next.js? Leia [Um formulário de contato em Next.js que envia e-mail](https://ikiform.com/pt-br/guides/send-form-to-email/nextjs.md).

**Astro**

O Zod confere cada campo na action antes de qualquer envio, e `isInputError` traz as mensagens dele por campo. A página precisa ser renderizada sob demanda, então mantenha `prerender = false` e adicione um adapter.

`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 = {
  sendMessage: defineAction({
    accept: "form",
    input: z.object({
      name: z.string().trim().min(1, "Tell us your name.").max(120),
      email: z.email("Enter an address we can reply to."),
      message: z.string().trim().min(10, "Write at least a sentence."),
      _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, page: "/contact" }),
      });
      const result = await response.json();
      if (!result.ok) throw new ActionError({ code: "BAD_REQUEST", message: result.error.message });
      return { email: input.email };
    },
  }),
};
```

`src/pages/contact.astro`:

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

export const prerender = false;

const result = Astro.getActionResult(actions.sendMessage);
const fields = result?.error && isInputError(result.error) ? result.error.fields : {};
---

{result?.data ? (
  <p role="status">Sent. We'll reply to {result.data.email}.</p>
) : (
  <form method="POST" action={actions.sendMessage}>
    <label for="name">Name</label>
    <input id="name" name="name" autocomplete="name" required />
    {fields.name && <p role="alert">{fields.name[0]}</p>}

    <label for="email">Your email</label>
    <input id="email" name="email" type="email" autocomplete="email" required />
    {fields.email && <p role="alert">{fields.email[0]}</p>}

    <label for="message">Message</label>
    <textarea id="message" name="message" rows="6" required minlength="10"></textarea>
    {fields.message && <p role="alert">{fields.message[0]}</p>}

    <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    {result?.error && !isInputError(result.error) && <p role="alert">{result.error.message}</p>}
    <button>Send message</button>
  </form>
)}
```

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. Cada exemplo de servidor repassa o header `Origin` do navegador quando existe, porque o `fetch` do próprio servidor não envia nenhum e o Ikiform usa esse header para contar os envios de teste gratuitos de `localhost`.

**TanStack Start**

A server function lê o `FormData` em `.validator()`, envia a partir do servidor e devolve o endereço de resposta ou um erro por campo.

`src/routes/contact.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";

type Outcome = { sent: true; email: string } | { sent: false; errors: Record<string, string> };

const sendMessage = createServerFn({ method: "POST" })
  .validator((data: unknown) => {
    if (!(data instanceof FormData)) throw new Error("Expected form data");
    return {
      name: String(data.get("name") ?? "").trim(),
      email: String(data.get("email") ?? "").trim(),
      message: String(data.get("message") ?? "").trim(),
      _gotcha: String(data.get("_gotcha") ?? ""),
    };
  })
  .handler(async ({ data }): Promise<Outcome> => {
    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, page: "/contact" }),
    });
    const result = await response.json();
    if (result.ok) return { sent: true, email: data.email };
    const errors: Record<string, string> = { form: String(result.error.message) };
    for (const [field, messages] of Object.entries(result.error.fields ?? {})) {
      errors[field] = String((messages as string[])[0]);
    }
    return { sent: false, errors };
  });

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

function Contact() {
  const send = useServerFn(sendMessage);
  const [outcome, setOutcome] = useState<Outcome | null>(null);
  const [pending, setPending] = useState(false);
  const errors = outcome && !outcome.sent ? outcome.errors : {};

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    setPending(true);
    try {
      setOutcome(await send({ data }));
    } catch {
      setOutcome({ sent: false, errors: { form: "Couldn't send your message. Try again." } });
    } finally {
      setPending(false);
    }
  }

  if (outcome?.sent) return <p role="status">Sent. We'll reply to {outcome.email}.</p>;

  return (
    <form method="post" onSubmit={onSubmit}>
      <label htmlFor="name">Name</label>
      <input id="name" name="name" autoComplete="name" required />
      {errors.name ? <p role="alert">{errors.name}</p> : null}
      <label htmlFor="email">Your email</label>
      <input id="email" name="email" type="email" autoComplete="email" required />
      {errors.email ? <p role="alert">{errors.email}</p> : null}
      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" rows={6} required minLength={10} />
      {errors.message ? <p role="alert">{errors.message}</p> : null}
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {errors.form && !errors.name && !errors.email && !errors.message ? <p role="alert">{errors.form}</p> : null}
      <button disabled={pending}>{pending ? "Sending" : "Send message"}</button>
    </form>
  );
}
```

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. Cada exemplo de servidor repassa o header `Origin` do navegador quando existe, porque o `fetch` do próprio servidor não envia nenhum e o Ikiform usa esse header para contar os envios de teste gratuitos de `localhost`.

**React Router**

A `action` da rota encaminha a mensagem e devolve os erros de campo do Ikiform via `actionData`, com o status code do Ikiform preservado por `data()`.

`app/routes/contact.tsx`:

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

export async function action({ request }: Route.ActionArgs) {
  const formData = await request.formData();
  const email = String(formData.get("email") ?? "").trim();
  const origin = 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 ${process.env.IKIFORM_SECRET_KEY}`,
      ...(origin ? { Origin: origin } : {}),
    },
    body: JSON.stringify({
      name: String(formData.get("name") ?? "").trim(),
      email,
      message: String(formData.get("message") ?? "").trim(),
      page: "/contact",
      _gotcha: String(formData.get("_gotcha") ?? ""),
    }),
  });
  const result = await response.json();
  if (result.ok) return { sent: true as const, email };
  const fields: Record<string, string[]> = result.error.fields ?? {};
  return data(
    { sent: false as const, message: String(result.error.message), fields },
    { status: response.status },
  );
}

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

  if (actionData?.sent) return <p role="status">Sent. We'll reply to {actionData.email}.</p>;
  const fields = actionData?.fields ?? {};

  return (
    <Form method="post">
      <label htmlFor="name">Name</label>
      <input id="name" name="name" autoComplete="name" required />
      {fields.name ? <p role="alert">{fields.name[0]}</p> : null}
      <label htmlFor="email">Your email</label>
      <input id="email" name="email" type="email" autoComplete="email" required />
      {fields.email ? <p role="alert">{fields.email[0]}</p> : null}
      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" rows={6} required minLength={10} />
      {fields.message ? <p role="alert">{fields.message[0]}</p> : null}
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {actionData && !Object.keys(fields).length ? <p role="alert">{actionData.message}</p> : null}
      <button disabled={pending}>{pending ? "Sending" : "Send message"}</button>
    </Form>
  );
}
```

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. Cada exemplo de servidor repassa o header `Origin` do navegador quando existe, porque o `fetch` do próprio servidor não envia nenhum e o Ikiform usa esse header para contar os envios de teste gratuitos de `localhost`.

**SvelteKit**

A action padrão devolve `fail()` com os erros de campo. `update({ reset: false })` mantém o que o visitante digitou quando o servidor responde com um erro. Declare `IKIFORM_SECRET_KEY` em `src/env.ts` com `defineEnvVars` para que `$app/env/private` consiga lê-la.

`src/routes/contact/+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 values = {
      name: String(data.get("name") ?? "").trim(),
      email: String(data.get("email") ?? "").trim(),
      message: String(data.get("message") ?? "").trim(),
    };

    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({ ...values, page: "/contact", _gotcha: String(data.get("_gotcha") ?? "") }),
    });
    const result = await response.json();
    if (!result.ok) {
      const fields: Record<string, string[]> = result.error.fields ?? {};
      return fail(response.status, { values, fields, message: String(result.error.message) });
    }
    return { sentTo: values.email };
  },
} satisfies Actions;
```

`src/routes/contact/+page.svelte`:

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

  let { form }: PageProps = $props();
  let pending = $state(false);
  const fields = $derived<Record<string, string[]>>(form?.fields ?? {});
</script>

{#if form?.sentTo}
  <p role="status">Sent. We'll reply to {form.sentTo}.</p>
{:else}
  <form
    method="POST"
    use:enhance={() => {
      pending = true;
      return async ({ update }) => {
        await update({ reset: false });
        pending = false;
      };
    }}
  >
    <label for="name">Name</label>
    <input id="name" name="name" autocomplete="name" required />
    {#if fields.name}<p role="alert">{fields.name[0]}</p>{/if}
    <label for="email">Your email</label>
    <input id="email" name="email" type="email" autocomplete="email" required />
    {#if fields.email}<p role="alert">{fields.email[0]}</p>{/if}
    <label for="message">Message</label>
    <textarea id="message" name="message" rows="6" required minlength="10"></textarea>
    {#if fields.message}<p role="alert">{fields.message[0]}</p>{/if}
    <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    {#if form?.message && !Object.keys(fields).length}<p role="alert">{form.message}</p>{/if}
    <button disabled={pending}>{pending ? "Sending" : "Send message"}</button>
  </form>
{/if}
```

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. Cada exemplo de servidor repassa o header `Origin` do navegador quando existe, porque o `fetch` do próprio servidor não envia nenhum e o Ikiform usa esse header para contar os envios de teste gratuitos de `localhost`.

**Nuxt**

Uma server route guarda a chave secreta e repassa a mensagem do Ikiform como o `message` do `createError`, e os erros de campo no `data` dele; o `$fetch` devolve os dois no cliente como `data.message` e `data.data.fields`. O Nuxt preenche `runtimeConfig.ikiformSecretKey` a partir de `NUXT_IKIFORM_SECRET_KEY`.

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

```ts
export default defineEventHandler(async (event) => {
  const body = await readBody<{ name?: string; email?: string; message?: 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({
      name: body.name?.trim() ?? "",
      email: body.email?.trim() ?? "",
      message: body.message?.trim() ?? "",
      page: "/contact",
      _gotcha: body._gotcha ?? "",
    }),
  });
  const result = await response.json();
  if (!result.ok) {
    throw createError({
      status: response.status,
      message: String(result.error.message),
      data: { fields: result.error.fields ?? {} },
    });
  }
  return { sentTo: body.email };
});
```

`nuxt.config.ts`:

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

`app/components/ContactForm.vue`:

```vue
<script setup lang="ts">
const values = reactive({ name: "", email: "", message: "", _gotcha: "" });
const fields = ref<Record<string, string[]>>({});
const error = ref("");
const sentTo = ref("");
const sending = ref(false);

async function send() {
  sending.value = true;
  fields.value = {};
  error.value = "";
  try {
    const result = await $fetch("/api/contact", { method: "POST", body: values });
    sentTo.value = result.sentTo ?? values.email;
  } catch (failure) {
    const data = (failure as { data?: { message?: string; data?: { fields?: Record<string, string[]> } } }).data;
    fields.value = data?.data?.fields ?? {};
    error.value = data?.message ?? "Couldn't send your message. Try again.";
  } finally {
    sending.value = false;
  }
}
</script>

<template>
  <p v-if="sentTo" role="status">Sent. We'll reply to {{ sentTo }}.</p>
  <form v-else @submit.prevent="send">
    <label for="name">Name</label>
    <input id="name" v-model="values.name" name="name" autocomplete="name" required />
    <p v-if="fields.name" role="alert">{{ fields.name[0] }}</p>
    <label for="email">Your email</label>
    <input id="email" v-model="values.email" name="email" type="email" autocomplete="email" required />
    <p v-if="fields.email" role="alert">{{ fields.email[0] }}</p>
    <label for="message">Message</label>
    <textarea id="message" v-model="values.message" name="message" rows="6" required minlength="10" />
    <p v-if="fields.message" role="alert">{{ fields.message[0] }}</p>
    <input v-model="values._gotcha" name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    <p v-if="error && !Object.keys(fields).length" role="alert">{{ error }}</p>
    <button :disabled="sending">{{ sending ? "Sending" : "Send message" }}</button>
  </form>
</template>
```

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. Cada exemplo de servidor repassa o header `Origin` do navegador quando existe, porque o `fetch` do próprio servidor não envia nenhum e o Ikiform usa esse header para contar os envios de teste gratuitos de `localhost`.

**React**

Sem um servidor próprio, o navegador envia direto ao Ikiform. Nenhuma chave entra neste código; **Allowed domains** decide quais sites podem enviar.

`src/ContactForm.tsx`:

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

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

export function ContactForm() {
  const [sentTo, setSentTo] = useState("");
  const [sending, setSending] = useState(false);
  const [fields, setFields] = useState<Record<string, string[]>>({});
  const [error, setError] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const body = new FormData(event.currentTarget);
    setSending(true);
    setFields({});
    setError("");
    try {
      const response = await fetch(ENDPOINT, { method: "POST", headers: { Accept: "application/json" }, body });
      const result = await response.json();
      if (result.ok) {
        setSentTo(String(body.get("email")));
        return;
      }
      setFields(result.error.fields ?? {});
      setError(result.error.message);
    } catch {
      setError("Couldn't reach the server. Try again.");
    } finally {
      setSending(false);
    }
  }

  if (sentTo) return <p role="status">Sent. We'll reply to {sentTo}.</p>;

  return (
    <form action={ENDPOINT} method="post" onSubmit={onSubmit}>
      <label htmlFor="name">Name</label>
      <input id="name" name="name" autoComplete="name" required />
      {fields.name ? <p role="alert">{fields.name[0]}</p> : null}
      <label htmlFor="email">Your email</label>
      <input id="email" name="email" type="email" autoComplete="email" required />
      {fields.email ? <p role="alert">{fields.email[0]}</p> : null}
      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" rows={6} required minLength={10} />
      {fields.message ? <p role="alert">{fields.message[0]}</p> : null}
      <input type="hidden" name="page" value="/contact" />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {error && !Object.keys(fields).length ? <p role="alert">{error}</p> : null}
      <button disabled={sending}>{sending ? "Sending" : "Send message"}</button>
    </form>
  );
}
```

**Vue**

O mesmo envio pelo navegador como componente Vue. O formulário mantém o `action`, então ainda envia se o script não carregar.

`src/components/ContactForm.vue`:

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

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

const sentTo = ref("");
const sending = ref(false);
const fields = ref<Record<string, string[]>>({});
const error = ref("");

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

<template>
  <p v-if="sentTo" role="status">Sent. We'll reply to {{ sentTo }}.</p>
  <form v-else :action="ENDPOINT" method="post" @submit.prevent="send">
    <label for="name">Name</label>
    <input id="name" name="name" autocomplete="name" required />
    <p v-if="fields.name" role="alert">{{ fields.name[0] }}</p>
    <label for="email">Your email</label>
    <input id="email" name="email" type="email" autocomplete="email" required />
    <p v-if="fields.email" role="alert">{{ fields.email[0] }}</p>
    <label for="message">Message</label>
    <textarea id="message" name="message" rows="6" required minlength="10" />
    <p v-if="fields.message" role="alert">{{ fields.message[0] }}</p>
    <input type="hidden" name="page" value="/contact" />
    <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    <p v-if="error && !Object.keys(fields).length" role="alert">{{ error }}</p>
    <button :disabled="sending">{{ sending ? "Sending" : "Send message" }}</button>
  </form>
</template>
```

- Troque `frm_xxxxxxxxxxxxxxxx` pelo id do seu formulário, que está no painel.
- `name="email"` transforma esse campo no endereço de reply-to do e-mail de notificação. Mantenha o nome, mesmo que o rótulo diga "Seu e-mail".
- `page` é um campo oculto comum. Ele aparece no e-mail, então você sabe de qual página veio a mensagem.
- `_gotcha` é o honeypot. As pessoas nunca o veem; um bot que o preenche é armazenado como spam e não manda e-mail para ninguém.
- `_redirect` só funciona com uma URL `https:` em um dos **Allowed domains** do formulário. Deixe-o de fora e defina **Redirect after submit** nas configurações do formulário, ou o Ikiform mostra a própria página de agradecimento.

Os guias de frameworks vão além: [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). Criadores de sites como o [Webflow](https://ikiform.com/pt-br/docs/guides/webflow.md) e o [Carrd](https://ikiform.com/pt-br/docs/guides/carrd.md) têm páginas próprias, e o [modelo de formulário de contato](https://ikiform.com/forms/contact-form) adiciona um campo de assunto.

Defina os campos no painel também, e o Ikiform recusa um envio que não corresponde com `422 validation_failed` e uma mensagem por campo, que o código acima mostra ao lado de cada input. Experimente aqui; isto roda o mesmo validador no seu navegador:

Formulário: Contact

- **Nome** (`name`, text, obrigatório)
- **Seu e-mail** (`email`, email, obrigatório)
- **Mensagem** (`message`, textarea, obrigatório)

Cole esta definição na aba **Fields** do formulário. Sem uma definição, o Ikiform aceita quaisquer nomes de campo e manda por e-mail o que chegar.

## Como é o e-mail

Todo envio aceito dispara um e-mail simples e legível. Esta é a versão em texto de um deles:

`E-mail de notificação`:

```text
From: Ikiform <notifications@ikiform.com>
To: you@yourcompany.com
Reply-To: ada@example.com
Subject: New submission: Contact

Contact got a new submission on Oct 25, 2026, 9:14 AM UTC.

name: Ada Lovelace
email: ada@example.com
message: Do you ship to Canada? I'd like to order 40 units before December.
page: /contact

View it in Ikiform:
https://ikiform.com/dashboard/submissions?form=frm_xxxxxxxxxxxxxxxx&submission=sub_...
```

- Ele vem de `notifications@ikiform.com`. Você não configura registros DNS nem domínio de envio, porque o e-mail sai do domínio do Ikiform.
- O assunto é sempre `New submission: <form name>`. `_subject` não muda o assunto dos e-mails de notificação. Ele é repassado aos webhooks e à API como `meta.subject`. Para diferenciar os formulários, dê a cada um um nome claro no painel, como "Contato, página de preços".
- Os campos aparecem na ordem em que foram enviados, identificados pelo `name`. Campos que começam com `_` ficam de fora.
- Um formulário ou uma resposta muito longa é encurtada, e o e-mail diz quantos campos a mais estão no painel. A [página de limites](https://ikiform.com/pt-br/docs/limits.md#notifications) tem os números.
- Campos que parecem senhas ou números de cartão ficam de fora, e o e-mail de um envio que os contém fica retido para revisão. Não colete credenciais com um backend de formulários.
- Membros do workspace recebem um botão **View submission**. Pessoas de fora do workspace recebem os campos e um link para cancelar a inscrição.

## Quem recebe o e-mail

Os destinatários são definidos por formulário no painel, nunca pelo próprio formulário. Um campo que indicasse um destinatário deixaria qualquer um usar seu formulário para mandar e-mail para qualquer pessoa, então o Ikiform não tem esse campo.

| Quem | Como é adicionado | Observações |
| --- | --- | --- |
| O padrão | Nada a fazer | O membro escolhido como **Notification email** do workspace, ou todos os donos do workspace quando nenhum é escolhido |
| Membros escolhidos | Marque-os nas configurações de **Notifications** do formulário | Só membros com endereço de e-mail verificado |
| Pessoas de fora do workspace | **Add someone outside the workspace**, depois **Send link** | Só recebem e-mails depois de confirmar. Até 5 por formulário, e cada um pode cancelar a inscrição |

Cada formulário manda e-mail para no máximo 10 endereços. Cada destinatário recebe a própria cópia em vez de um CC compartilhado, então ninguém vê os endereços dos outros. Se a caixa de um destinatário devolve os e-mails, o Ikiform pausa os envios para esse endereço por um tempo e o painel mostra **Emails bounced** ao lado dele. [Vários destinatários](https://ikiform.com/pt-br/guides/send-form-to-email/multiple-recipients.md) explica o roteamento por assunto, que os destinatários sozinhos não resolvem.

## Responda à pessoa, não ao Ikiform

O Ikiform define o endereço de reply-to primeiro a partir de um campo `_replyto`, depois a partir de um campo chamado `email`, `email_address`, `your_email`, `reply_to`, `contact_email`. O valor precisa ser um único endereço de e-mail válido, ou é ignorado. Com um reply-to definido, clicar em **Reply** no Gmail, no Outlook ou no Apple Mail abre uma resposta para o visitante, e sua resposta sai do seu próprio endereço.

Sem ele, uma resposta vai para `notifications@ikiform.com`, que ninguém lê. Essa é a surpresa mais comum, e geralmente é um campo chamado `user_email` ou `Email Address` com espaço. Renomeie-o ou adicione `_replyto`. [O guia de reply-to](https://ikiform.com/pt-br/guides/send-form-to-email/reply-to.md) cobre formulários com dois campos de e-mail e criadores de sites que renomeiam os campos por você.

## Quando chega muita coisa de uma vez

Cada formulário envia até 50 e-mails de notificação por hora, ou 10 enquanto o workspace tem menos de 7 dias. Passando disso, o resto dos envios da hora é armazenado normalmente e chega num único e-mail de resumo quando a hora termina. O limite protege sua caixa de entrada, e a reputação dos e-mails do Ikiform com o seu provedor, quando um formulário é divulgado em algum lugar grande ou um bot passa pelos filtros.

Diagrama: O que acontece com os e-mails de notificação numa hora movimentada

1. Os 50 primeiros envios para um formulário numa hora cheia disparam um e-mail cada, como de costume.
2. Os envios seguintes na mesma hora são armazenados e aparecem no painel na hora, sem um e-mail para cada um.
3. Quando a hora termina, cada destinatário recebe um resumo que conta esses envios e leva ao painel.

O limite é contado por formulário, então uma rajada num formulário não atrasa outro. Os webhooks não têm limite e continuam disparando para cada envio.

O assunto do resumo é a contagem e o nome do formulário, e ele não lista os campos nem define reply-to, então responda essas mensagens pelo painel. Se você passa do limite com frequência, o e-mail não é o canal certo para esse formulário: envie para o [Slack](https://ikiform.com/blog/form-submissions-to-slack.md) ou para uma caixa compartilhada por meio de um [webhook](https://ikiform.com/pt-br/docs/webhooks.md).

## Mantenha o spam fora da sua caixa de entrada

O e-mail deixa o spam caro: cada envio de bot é uma notificação que alguém abre. O Ikiform armazena spam com uma resposta de sucesso normal, então o bot não aprende nada, e não manda e-mail por ele. Quatro coisas decidem o que conta como spam:

| Defesa | O que ela pega | Configuração |
| --- | --- | --- |
| Honeypot | Bots que preenchem todos os inputs | O input oculto `_gotcha` |
| Pontuação de spam | Links no campo de nome, três ou mais links, frases de spam conhecidas, endereços descartáveis e repetições idênticas. Com 5 o envio é spam | Nenhuma |
| Domínios permitidos | Envios de outros sites | Adicione seu domínio nas configurações do formulário |
| Verificação humana | Bots que rodam um navegador de verdade | Defina **Spam check** como **Ikiform** |

Confira o filtro **Spam** no painel de vez em quando. Se uma mensagem real cair lá, marque-a como não spam e o e-mail dela é enviado na hora. A [documentação de spam](https://ikiform.com/pt-br/docs/spam.md) explica cada sinal, inclusive quando ligar a verificação humana.

## O que o e-mail não faz

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). Se o seu site promete "recebemos sua mensagem", mostre isso na página depois do envio, onde o código acima já mostra.

- **Uma caixa de entrada compartilhada da equipe.** Adicione o endereço da equipe como destinatário ou encaminhe pelo seu provedor de e-mail.
- **Um canal de chat.** Envie cada envio para o [Slack](https://ikiform.com/blog/form-submissions-to-slack.md), o [Discord](https://ikiform.com/blog/form-submissions-to-discord.md) ou o [Microsoft Teams](https://ikiform.com/blog/form-submissions-to-microsoft-teams.md) por webhook. O chat combina com formulários que recebem mais do que algumas mensagens por dia.
- **Uma planilha ou CRM.** O [Google Sheets](https://ikiform.com/blog/form-submissions-to-google-sheets.md), ou qualquer ferramenta via [Zapier](https://ikiform.com/blog/form-submissions-to-zapier.md) ou [n8n](https://ikiform.com/blog/form-submissions-to-n8n.md).
- **Anexos.** Upload de arquivos ainda não é suportado. Um envio com arquivo recebe `422 files_not_supported`. Peça um link para um arquivo compartilhado.

### Desligando o e-mail

Alguns formulários ficam melhores sem e-mail, como uma inscrição de newsletter movimentada. Desligue **Email new submissions** nas configurações de **Notifications** do formulário. Os envios continuam sendo armazenados, e os webhooks continuam disparando.

> O limite começa mais baixo em workspaces novos: 10 e-mails por formulário por hora nos primeiros 7 dias. Se você está testando com um script, espere receber um resumo mais cedo do que receberia depois.

## Escolha o seu caso

A configuração acima é a mesma em todo lugar. Estas páginas tratam de uma caixa de entrada específica, de uma stack específica ou da ferramenta que você está deixando para trás.

- [Sem PHP](https://ikiform.com/pt-br/guides/send-form-to-email/without-php.md): Substitua um script contact.php que chama mail() por um formulário que envia a um endpoint hospedado, mantendo seus nomes de campo, sua página de agradecimento e as respostas.
- [Para o Gmail](https://ikiform.com/pt-br/guides/send-form-to-email/gmail.md): Receba os envios de formulário no Gmail sem links mailto:, senhas de app ou Apps Script, e depois rotule, tire do spam e responda pelo Gmail.
- [Vários destinatários](https://ikiform.com/pt-br/guides/send-form-to-email/multiple-recipients.md): Mande um formulário por e-mail para a equipe toda, ou encaminhe cada mensagem à pessoa certa pelo assunto, sem deixar o próprio formulário escolher quem recebe.
- [Responder pela caixa de entrada](https://ikiform.com/pt-br/guides/send-form-to-email/reply-to.md): Faça o Reply de uma notificação de formulário responder a quem preencheu: quais nomes de campo funcionam, quando usar _replyto e por que as respostas se perdem.
- [Alternativa ao EmailJS](https://ikiform.com/pt-br/guides/send-form-to-email/emailjs-alternative.md): Tire um formulário de contato do EmailJS: por que chaves de e-mail no navegador são um risco, o que muda no seu código e do que você abre mão, como a resposta automática.
- [Next.js](https://ikiform.com/pt-br/guides/send-form-to-email/nextjs.md): Duas formas de mandar um formulário Next.js por e-mail com uma Server Action: enviar você mesmo com Resend ou Nodemailer, ou encaminhar a um backend de formulários, comparadas com honestidade.

## Confira se funciona

Siga estes passos antes de colocar o formulário no ar. 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. Nas configurações de **Notifications** do formulário, confira se **Email new submissions** está ligado e se as pessoas certas estão marcadas.
3. Preencha o formulário na sua página e envie. Você cai na sua página de agradecimento, ou na hospedada pelo Ikiform.
4. Abra **Submissions** no painel. O novo envio está lá com todos os campos que você enviou.
5. Abra o e-mail de notificação. O assunto é `New submission:` mais o nome do formulário, e todos os campos que você enviou estão listados.
6. Clique em **Reply**. A linha **To** mostra o endereço que você digitou no formulário, não o do Ikiform.
7. 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.
8. Não chegou nada? Mude o filtro de status do painel para **Spam**. Spam recebe a mesma resposta de sucesso de um envio real.

## Solução de problemas

| Problema | O que fazer |
| --- | --- |
| O envio está no painel, mas nenhum e-mail chegou | Olhe primeiro na pasta de spam ou de promoções e adicione `notifications@ikiform.com` aos seus contatos. Depois confira as configurações de **Notifications** do formulário: o e-mail pode estar desligado, um endereço de fora pode ainda mostrar **Waiting for confirmation**, ou pode aparecer **Emails bounced**. |
| 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. |
| **Reply** vai para notifications@ikiform.com | Nenhum reply-to foi encontrado. Dê ao campo de e-mail um destes nomes: `email`, `email_address`, `your_email`, `reply_to`, `contact_email`, ou adicione um campo `_replyto` com o endereço do visitante. |
| Os e-mails pararam e chegou um resumo no lugar | O formulário passou de 50 e-mails numa hora (10 num workspace novo). Está tudo no painel. Procure uma rajada de bots em **Spam**, ou use um webhook para formulários movimentados. |
| `_subject` não muda o assunto | `_subject` não muda o assunto dos e-mails de notificação. Ele é repassado aos webhooks e à API como `meta.subject`. |
| `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. |
| `422 validation_failed` | O envio não corresponde à definição de campos do formulário. `error.fields` aponta cada campo e o motivo. Faça o `name` do input bater com o id do campo. |
| `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

### Um formulário HTML consegue enviar e-mail sem servidor?

Não sozinho. Um formulário só faz uma requisição HTTP, e enviar e-mail exige um servidor que converse com um servidor de e-mail. Um backend de formulários é esse servidor: aponte o action do formulário para ele e ele manda o e-mail para você, então o seu site pode continuar estático.

### Por que não usar mailto: no action do formulário?

Um action mailto: não envia nada. Ele pede ao computador do visitante que abra um app de e-mail com um rascunho, o que falha para quem não tem um configurado, e o visitante ainda precisa clicar em Enviar. Muita gente desiste aí, e você nunca fica sabendo que a pessoa tentou.

### Posso mudar o assunto do e-mail de notificação?

Não. O assunto é sempre "New submission:" seguido do nome do formulário, então dê a cada formulário um nome claro. Um campo _subject é armazenado e repassado aos webhooks e à API, mas não muda o e-mail.

### Quantas pessoas podem receber o e-mail?

Até 10 por formulário: membros do workspace que você marca nas configurações do formulário, mais até 5 endereços de fora que confirmam por link. Cada um recebe a própria cópia.

### O Ikiform pode enviar e-mail para quem preencheu o formulário?

Não. O Ikiform envia e-mail para você e sua equipe, não para quem enviou, então não há resposta automática nem e-mail de confirmação. Envie um webhook para sua ferramenta de e-mail e mande a confirmação de lá.

### Os e-mails do formulário vão cair no spam?

Eles saem do próprio domínio do Ikiform, então você não precisa de registros SPF ou DKIM para o seu domínio. O primeiro ainda pode cair em spam ou promoções; adicione notifications@ikiform.com aos seus contatos ou crie um filtro para que isso nunca aconteça.

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

- [Sem PHP](https://ikiform.com/pt-br/guides/send-form-to-email/without-php.md): Substitua um script contact.php que chama mail() por um formulário que envia a um endpoint hospedado, mantendo seus nomes de campo, sua página de agradecimento e as respostas.
- [Para o Gmail](https://ikiform.com/pt-br/guides/send-form-to-email/gmail.md): Receba os envios de formulário no Gmail sem links mailto:, senhas de app ou Apps Script, e depois rotule, tire do spam e responda pelo Gmail.
- [Vários destinatários](https://ikiform.com/pt-br/guides/send-form-to-email/multiple-recipients.md): Mande um formulário por e-mail para a equipe toda, ou encaminhe cada mensagem à pessoa certa pelo assunto, sem deixar o próprio formulário escolher quem recebe.
- [Responder pela caixa de entrada](https://ikiform.com/pt-br/guides/send-form-to-email/reply-to.md): Faça o Reply de uma notificação de formulário responder a quem preencheu: quais nomes de campo funcionam, quando usar _replyto e por que as respostas se perdem.
- [Alternativa ao EmailJS](https://ikiform.com/pt-br/guides/send-form-to-email/emailjs-alternative.md): Tire um formulário de contato do EmailJS: por que chaves de e-mail no navegador são um risco, o que muda no seu código e do que você abre mão, como a resposta automática.
- [Next.js](https://ikiform.com/pt-br/guides/send-form-to-email/nextjs.md): Duas formas de mandar um formulário Next.js por e-mail com uma Server Action: enviar você mesmo com Resend ou Nodemailer, ou encaminhar a um backend de formulários, comparadas com honestidade.
- [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.
- [Vá além dos formulários do criador](https://ikiform.com/pt-br/guides/website-builder-forms.md): Quando os formulários do Webflow, Netlify, Carrd ou Framer batem num limite ou não têm um recurso, mantenha o formulário que você desenhou e aponte para um backend de formulários.
- [Formulário de feedback para um site ou app](https://ikiform.com/pt-br/guides/feedback-form.md): Coloque um formulário de feedback em qualquer página ou dentro do seu app: uma nota, um comentário, a página de origem, filtro de spam e alertas para notas baixas.
