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

Mailbeam
Clerk + Next.jsIntermedio20 minutosActualizado en enero de 2025

Verificación de email con Clerk

Clerk admite webhooks para los eventos del ciclo de vida del usuario, incluido user.created. Clerk no tiene un bloqueo nativo previo a la creación, pero puedes combinar un webhook con los metadatos de usuario de Clerk para marcar los registros inválidos y borrarlos de inmediato, o usar el patrón de redirección previa al registro.

Este tutorial usa el patrón recomendado: verificar el email en un endpoint del servidor antes de llamar a signUp.create de Clerk.

Requisitos previos

  • Una cuenta de Clerk y un proyecto de Next.js con @clerk/nextjs
  • Una clave de API de Mailbeam (regístrate gratis)

Preparación

npm install svix  # svix es la biblioteca de webhooks de Clerk

Endpoint de verificación en el servidor

// app/api/verify-email/route.ts
import { NextResponse } from "next/server";
// Todavía no hay SDK: este es el envoltorio de 12 líneas de /docs/quickstart.
import { verifyEmail } from "./lib/mailbeam";

export async function POST(req: Request) {
  const { email } = await req.json();

  if (!email) {
    return NextResponse.json({ error: "falta el email" }, { status: 400 });
  }

  try {
    const { valid, score, reason } = await verifyEmail(email);

    return NextResponse.json({ valid, score, reason });
  } catch {
    // Falla en abierto
    return NextResponse.json({ valid: true, score: 50, reason: null });
  }
}

Formulario de registro propio

// app/signup/CustomSignupForm.tsx
"use client";

import { useSignUp } from "@clerk/nextjs";
import { useState } from "react";

export function CustomSignupForm() {
  const { signUp, setActive } = useSignUp();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setLoading(true);
    setError(null);

    // Paso 1: verifica el email en el servidor antes de llamar a Clerk
    const verifyRes = await fetch("/api/verify-email", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email }),
    });

    const { valid, score, reason } = await verifyRes.json();

    if (!valid || score < 60) {
      setError(
        reason === "disposable_domain"
          ? "Usa una dirección de email permanente."
          : "Escribe una dirección de email válida."
      );
      setLoading(false);
      return;
    }

    // Paso 2: sigue con el registro en Clerk
    try {
      const result = await signUp!.create({ emailAddress: email, password });

      if (result.status === "complete") {
        await setActive!({ session: result.createdSessionId });
        window.location.href = "/dashboard";
      }
    } catch (err: unknown) {
      setError((err as { errors?: { message: string }[] })?.errors?.[0]?.message ?? "El registro ha fallado.");
    } finally {
      setLoading(false);
    }
  }

  return (
    <form onSubmit={handleSubmit} className="space-y-4 max-w-sm">
      {error && (
        <p role="alert" className="rounded-lg bg-destructive/10 p-3 text-sm text-destructive">
          {error}
        </p>
      )}
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Dirección de email"
        className="w-full rounded-lg border border-border px-3 py-2 text-sm bg-background"
        required
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Contraseña"
        className="w-full rounded-lg border border-border px-3 py-2 text-sm bg-background"
        required
      />
      <button
        type="submit"
        disabled={loading}
        className="w-full rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground disabled:opacity-60"
      >
        {loading ? "Creando la cuenta…" : "Crear cuenta"}
      </button>
    </form>
  );
}

Buenas prácticas

  • Verifica antes de llamar a Clerk, no después: evita usuarios huérfanos sin verificar
  • Mantén ligero el endpoint de verificación: solo debe llamar a Mailbeam y devolver
  • Usa reason para dar mensajes de error concretos

Siguientes pasos