Beta privadaMailbeam está en beta privada: la API pública todavía no está abierta.Únete a la lista de espera

Mailbeam
Vue 3 + Composition APIIntermedio20 minutosActualizado en enero de 2025

Verificación de email en Vue 3

Este tutorial construye un composable reutilizable useEmailVerification para la Composition API de Vue 3. Da feedback instantáneo mientras la persona escribe —válido, inválido o «¿querías decir gmail.com?»— manteniendo tu clave secreta de API a salvo en el servidor.

Qué vas a construir

  • Un endpoint pequeño en el servidor que hace de proxy de la verificación (para que la clave nunca llegue al navegador)
  • Un composable useEmailVerification con debounce que devuelve estado reactivo
  • Un formulario de registro con estado en línea, sugerencias de erratas y una reverificación al enviar

Requisitos previos

  • Vue 3 (Vite o Nuxt) con TypeScript
  • Una ruta de backend bajo tu control (Node, Python, etc.) para el proxy
  • Una clave de API de Mailbeam (regístrate gratis)

Paso 1 — Añade un endpoint de verificación en el servidor

Nunca llames a Mailbeam directamente desde el navegador: expondrías tu clave. Añade un proxy fino en tu backend. Aquí está en Node/Express:

// server/routes/verify-email.js
// Todavía no hay SDK: este es el envoltorio de 12 líneas de /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: "falta el email" });

  try {
    const { valid, score, reason, didYouMean } = await verifyEmail(email);
    res.json({ acceptable: valid && score >= 60, reason, didYouMean });
  } catch (err) {
    // Falla en abierto: no bloquees la interfaz por un error de la API
    res.json({ acceptable: true, reason: null, didYouMean: null });
  }
}

Paso 2 — Crea el 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"; // falla en abierto en la interfaz
      reason.value = null;
    }
  }

  // Debounce: espera 500 ms tras dejar de escribir
  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 };
}

Paso 3 — Conéctalo a un formulario

<!-- 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() {
  // Reverifica en el servidor al enviar: nunca te fíes solo del estado del cliente
  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) {
    // muestra el error de validación del servidor
  }
}

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">Comprobando…</p>
    <p v-else-if="status === 'valid'" class="hint ok">✓ Tiene buena pinta</p>
    <p v-else-if="status === 'invalid'" class="hint err">
      Este email parece no entregable ({{ reason }}).
    </p>

    <p v-if="suggestion" class="hint">
      ¿Querías decir
      <button type="button" @click="applySuggestion">{{ suggestion }}</button>?
    </p>

    <input v-model="password" type="password" autocomplete="new-password" />

    <button type="submit" :disabled="!canSubmit">Crear cuenta</button>
  </form>
</template>

Paso 4 — Verifica siempre también en el servidor

El composable mejora la experiencia, pero el estado del cliente se puede falsear o saltar. Tu handler de /api/signup tiene que ejecutar la misma verificación antes de crear el usuario: reutiliza la lógica del proxy del paso 1.

Buenas prácticas

PrácticaPor qué
Debounce de ~500 msUna llamada por pausa, no por pulsación
Abortar las peticiones en vueloEvita resultados desordenados mientras se escribe
Pasar por el proxy de tu servidorMantiene la clave de API fuera del cliente
Reverificar al enviarEl estado del cliente es orientativo, no autoritativo
Mostrar didYouMeanRescata a quien escribe gmial.com

Checklist de producción

  • Endpoint de verificación con límite de tasa por IP
  • MAILBEAM_KEY solo en el servidor
  • El handler de envío reverifica en el servidor
  • La interfaz falla en abierto si la comprobación da error
  • Los estados de carga y de error son accesibles (aria-live)

Siguientes pasos