← Tutte le idee Strumenti online

Web3Forms

Il modulo "contattaci" per qualsiasi sito, senza programmare un server

Incasso mensile: 43.000 USD (fonte indicata nella scheda)

Cosa fa

Chi ha un sito semplice (fatto con HTML, Webflow, Framer…) spesso non sa dove far arrivare i messaggi del modulo contatti. Questo servizio dà un indirizzo a cui collegare il modulo: i messaggi arrivano per email, con filtro antispam.

Come guadagna

Ha entrate ricorrenti ogni mese, verificate su TrustMRR. Prezzi e piani esatti sono indicati sul sito del progetto.

Rendilo tuo

  • Pensato per le attività italiane: messaggi anche su WhatsApp o Telegram, non solo per email.
  • Aggiungi le richieste di preventivo con allegati foto, utili per artigiani e officine.
  • Dashboard in italiano con tutte le richieste ricevute e lo stato (da richiamare, fatto).

Le frasi da scrivere all'AI

1. Base del sito e grafica

Crea un'app web in italiano chiamata "Modulino": dà a chiunque abbia un sito un indirizzo a cui collegare il modulo "contattaci": i messaggi arrivano per email e si leggono in un pannello. Usa React, Vite, TypeScript e Tailwind CSS.

Pagine da creare:
1. Pagina iniziale con il codice di esempio da copiare, i vantaggi e i prezzi
2. Registrazione e accesso
3. Pannello con i moduli creati
4. Pagina di un modulo: codice da incollare, impostazioni e messaggi ricevuti
5. Documentazione semplice: come collegarlo a WordPress, Webflow, HTML

Stile: pulito e tecnico ma amichevole: sfondo bianco, testo #111827, accento viola #6D28D9, blocchi di codice su fondo scuro, carattere "Inter" e "JetBrains Mono" per il codice. Nessuna immagine generica di stock.

Tutto deve funzionare perfettamente da telefono. Tutti i testi in italiano, chiari e senza parole tecniche.

2. Database, account e sicurezza

Collega il progetto a Supabase e attiva la registrazione con email e password.

Crea queste tabelle:
- "moduli": nome, email a cui inoltrare, chiave pubblica del modulo, collegati all'utente
- "messaggi": i dati inviati da ogni modulo, con data e un riferimento anonimo al mittente per bloccare gli abusi

Regole di sicurezza obbligatorie:
- ogni utente vede solo i suoi moduli e i loro messaggi
- i messaggi li scrive solo la Edge Function pubblica, dopo i controlli antispam

Usa esattamente il codice SQL che trovi nei "Dettagli tecnici" di questa scheda: eseguilo nel SQL Editor di Supabase prima di continuare, poi collega le pagine alle tabelle.

3. L'indirizzo che riceve i moduli

Crea la Edge Function pubblica "invia" (codice nei Dettagli tecnici) che riceve i moduli da qualsiasi sito:
1. legge la chiave del modulo e controlla che esista;
2. scarta i messaggi con il campo nascosto antispam compilato (i robot lo riempiono, le persone no);
3. limita a 10 messaggi all'ora per mittente;
4. salva il messaggio nella tabella "messaggi";
5. invia un'email al proprietario con tutti i campi, tramite Resend;
6. reindirizza il visitatore a una pagina di ringraziamento (quella del modulo o una predefinita).

Nella pagina di ogni modulo mostra il codice HTML pronto da copiare, con la chiave già inserita e il pulsante "Copia".

4. Pannello dei messaggi

Nella pagina di un modulo mostra i messaggi ricevuti, dal più recente, con ricerca, segno "letto/da leggere" e pulsante per esportare tutto in CSV. Impostazioni del modulo: email di destinazione (anche più di una), oggetto dell'email, pagina di ringraziamento e domini autorizzati a usare il modulo.

Mostra in alto il contatore dei messaggi del mese rispetto al limite del piano.

5. Pagamenti con Stripe

Aggiungi un abbonamento a 5 € al mese. Gratis l'utente ha: 100 messaggi al mese e un solo modulo. Con il piano Pro sblocca: messaggi e moduli illimitati, allegati, notifiche su Telegram e nessun marchio nell'email.

A pagamento riuscito il webhook porta l'utente al piano Pro; se l'abbonamento viene disdetto o non rinnovato lo riporta al piano gratuito.

Per la sessione di pagamento e il webhook segui la guida "Pagamenti con Stripe" di Fattibile.

6. Controlli specifici di questo progetto

Oltre ai controlli della guida "Controllo finale", verifica in particolare:
1. Antispam: invia 20 messaggi di seguito dallo stesso indirizzo e un messaggio con il campo nascosto compilato: devono essere bloccati.
Correggi quello che non va e scrivimi cosa hai cambiato.

Dettagli tecnici

Tabelle del database

Moduli e messaggi ricevuti. I messaggi li scrive solo la Edge Function.

create table moduli (
  id uuid primary key default gen_random_uuid(),
  utente_id uuid not null references auth.users(id) on delete cascade,
  chiave uuid not null unique default gen_random_uuid(),
  nome text not null,
  email_destinazione text not null,
  pagina_grazie text,
  creato_il timestamptz not null default now()
);

create table messaggi (
  id uuid primary key default gen_random_uuid(),
  modulo_id uuid not null references moduli(id) on delete cascade,
  dati jsonb not null,
  mittente_hash text not null,          -- IP cifrato, mai salvato in chiaro
  letto boolean not null default false,
  creato_il timestamptz not null default now()
);

alter table moduli enable row level security;
create policy "le mie righe" on moduli
  for all using (auth.uid() = utente_id) with check (auth.uid() = utente_id);

alter table messaggi enable row level security;
create policy "messaggi dei miei moduli" on messaggi
  for select using (exists (
    select 1 from moduli m where m.id = modulo_id and m.utente_id = auth.uid()
  ));

La Edge Function che riceve i moduli

Riceve il modulo da qualsiasi sito, blocca robot e abusi, salva il messaggio e manda l'email. Va pubblicata con --no-verify-jwt perché è pubblica.

// supabase/functions/invia/index.ts
import { createClient } from "npm:@supabase/supabase-js";

const db = createClient(Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!);

async function hash(testo: string) {
  const dati = new TextEncoder().encode(testo + Deno.env.get("SALE_SEGRETO"));
  const h = await crypto.subtle.digest("SHA-256", dati);
  return [...new Uint8Array(h)].map((b) => b.toString(16).padStart(2, "0")).join("");
}

Deno.serve(async (req) => {
  const form = await req.formData();
  const chiave = String(form.get("chiave") ?? "");
  if (form.get("_sito_web")) return new Response("ok"); // campo nascosto: è un robot

  const { data: modulo } = await db.from("moduli").select("*").eq("chiave", chiave).single();
  if (!modulo) return new Response("Modulo non trovato", { status: 404 });

  const mittente = await hash(req.headers.get("x-forwarded-for") ?? "sconosciuto");
  const unOraFa = new Date(Date.now() - 3_600_000).toISOString();
  const { count } = await db.from("messaggi").select("id", { count: "exact", head: true })
    .eq("mittente_hash", mittente).gte("creato_il", unOraFa);
  if ((count ?? 0) >= 10) return new Response("Troppi messaggi, riprova più tardi", { status: 429 });

  const dati = Object.fromEntries([...form.entries()].filter(([k]) => !k.startsWith("_") && k !== "chiave"));
  await db.from("messaggi").insert({ modulo_id: modulo.id, dati, mittente_hash: mittente });

  await fetch("https://api.resend.com/emails", {
    method: "POST",
    headers: { Authorization: `Bearer ${Deno.env.get("RESEND_API_KEY")}`, "Content-Type": "application/json" },
    body: JSON.stringify({
      from: "Modulino <avvisi@tuodominio.it>",
      to: modulo.email_destinazione,
      subject: `Nuovo messaggio da ${modulo.nome}`,
      text: Object.entries(dati).map(([k, v]) => `${k}: ${v}`).join("\n"),
    }),
  });

  return Response.redirect(modulo.pagina_grazie ?? "https://tuodominio.it/grazie", 303);
});

Il codice da incollare nel sito

Il modulo che il cliente copia nel suo sito. Il campo nascosto "_sito_web" serve a riconoscere i robot.

<form action="https://TUO-PROGETTO.supabase.co/functions/v1/invia" method="POST">
  <input type="hidden" name="chiave" value="LA-CHIAVE-DEL-MODULO">
  <!-- Trappola per robot: le persone non la vedono -->
  <input type="text" name="_sito_web" style="display:none" tabindex="-1" autocomplete="off">

  <label>Nome <input name="nome" required></label>
  <label>Email <input name="email" type="email" required></label>
  <label>Messaggio <textarea name="messaggio" required></textarea></label>
  <button type="submit">Invia</button>
</form>

Pezzi comuni a tutti i progetti

Glossario

JSONB
Un tipo di colonna che contiene dati strutturati flessibili, come un piccolo documento.
CSV
Un file di testo con i dati separati da virgole, che si apre con Excel.