Verifica delle email in Vue 3
Questo tutorial costruisce un composable riutilizzabile useEmailVerification per la Composition API di Vue 3. Dà un riscontro immediato mentre la persona scrive — valido, non valido oppure «intendevi gmail.com?» — tenendo la tua chiave segreta al sicuro sul server.
Che cosa costruirai
- Un piccolo endpoint sul server che fa da proxy alla verifica (così la chiave non arriva mai al browser)
- Un composable
useEmailVerificationcon debounce che restituisce stato reattivo - Un modulo di registrazione con stato in linea, suggerimenti sui refusi e una riverifica all'invio
Prerequisiti
- Vue 3 (Vite o Nuxt) con TypeScript
- Una rotta di backend sotto il tuo controllo (Node, Python, eccetera) per il proxy
- Una chiave API di Mailbeam (iscriviti gratis)
Passo 1 — Aggiungi un endpoint di verifica sul server
Non chiamare mai Mailbeam direttamente dal browser: esporresti la tua chiave. Aggiungi un proxy sottile nel tuo backend. Eccolo in Node/Express:
// server/routes/verify-email.js
// Non ci sono ancora SDK: questo è il wrapper di 12 righe di /docs/quickstart.
import { verifyEmail } from "./lib/mailbeam";
export async function verifyEmail(req, res) {
const { email } = req.body;
if (!email) return res.status(400).json({ error: "email mancante" });
try {
const { valid, score, reason, didYouMean } = await verifyEmail(email);
res.json({ acceptable: valid && score >= 60, reason, didYouMean });
} catch (err) {
// Lascia passare: non bloccare l'interfaccia per un errore dell'API
res.json({ acceptable: true, reason: null, didYouMean: null });
}
}Passo 2 — Crea il composable
// src/composables/useEmailVerification.ts
import { ref, watch } from "vue";
type Status = "idle" | "checking" | "valid" | "invalid" | "error";
export function useEmailVerification(emailRef: import("vue").Ref<string>) {
const status = ref<Status>("idle");
const reason = ref<string | null>(null);
const suggestion = ref<string | null>(null);
let controller: AbortController | null = null;
let timer: ReturnType<typeof setTimeout> | null = null;
async function check(email: string) {
controller?.abort();
controller = new AbortController();
status.value = "checking";
try {
const res = await fetch("/api/verify-email", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email }),
signal: controller.signal,
});
const data = await res.json();
suggestion.value = data.didYouMean ?? null;
if (data.acceptable) {
status.value = "valid";
reason.value = null;
} else {
status.value = "invalid";
reason.value = data.reason ?? "invalid_email";
}
} catch (err) {
if ((err as Error).name === "AbortError") return;
status.value = "error"; // nell'interfaccia lascia passare
reason.value = null;
}
}
// Debounce: aspetta 500 ms dopo che si è smesso di scrivere
watch(emailRef, (email) => {
if (timer) clearTimeout(timer);
suggestion.value = null;
if (!email || !email.includes("@")) {
status.value = "idle";
return;
}
timer = setTimeout(() => check(email), 500);
});
return { status, reason, suggestion };
}Passo 3 — Collegalo a un modulo
<!-- src/components/SignupForm.vue -->
<script setup lang="ts">
import { ref, computed } from "vue";
import { useEmailVerification } from "@/composables/useEmailVerification";
const email = ref("");
const password = ref("");
const { status, reason, suggestion } = useEmailVerification(email);
const canSubmit = computed(
() => status.value === "valid" && password.value.length >= 8
);
async function onSubmit() {
// Riverifica sul server all'invio: non fidarti mai del solo stato del client
const res = await fetch("/api/signup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: email.value, password: password.value }),
});
if (!res.ok) {
// mostra l'errore di validazione del server
}
}
function applySuggestion() {
if (suggestion.value) email.value = suggestion.value;
}
</script>
<template>
<form @submit.prevent="onSubmit">
<label for="email">Email</label>
<input id="email" v-model="email" type="email" autocomplete="email" />
<p v-if="status === 'checking'" class="hint">Controllo in corso…</p>
<p v-else-if="status === 'valid'" class="hint ok">✓ Sembra a posto</p>
<p v-else-if="status === 'invalid'" class="hint err">
Questa email sembra non recapitabile ({{ reason }}).
</p>
<p v-if="suggestion" class="hint">
Intendevi
<button type="button" @click="applySuggestion">{{ suggestion }}</button>?
</p>
<input v-model="password" type="password" autocomplete="new-password" />
<button type="submit" :disabled="!canSubmit">Crea account</button>
</form>
</template>Passo 4 — Verifica sempre anche sul server
Il composable migliora l'esperienza, ma lo stato del client si può falsificare o saltare. Il tuo gestore di /api/signup deve eseguire la stessa verifica prima di creare l'utente: riusa la logica del proxy del passo 1.
Buone pratiche
| Pratica | Perché |
|---|---|
| Debounce di ~500 ms | Una chiamata per pausa, non per tasto premuto |
| Interrompere le richieste in volo | Evita risultati fuori ordine mentre si scrive |
| Passare dal proxy sul tuo server | Tiene la chiave API fuori dal client |
| Riverificare all'invio | Lo stato del client è indicativo, non autorevole |
Mostrare didYouMean | Salva chi scrive gmial.com |
Lista di controllo per la produzione
- Endpoint di verifica con limite di ritmo per IP
-
MAILBEAM_KEYsolo sul server - Il gestore dell'invio riverifica sul server
- L'interfaccia lascia passare se il controllo va in errore
- Gli stati di caricamento e di errore sono accessibili (aria-live)