Bêta privéeMailbeam est en bêta privée : l'API publique n'est pas encore ouverte.Rejoindre la liste d'attente

Mailbeam
Clerk + Next.jsIntermédiaire20 minutesMis à jour en janvier 2025

Vérification d'e-mails avec Clerk

Clerk gère des webhooks pour les événements du cycle de vie utilisateur, dont user.created. Clerk n'offre pas de blocage natif avant création, mais vous pouvez combiner un webhook et les métadonnées utilisateur pour marquer les inscriptions invalides en vue d'une suppression immédiate, ou employer le motif de redirection avant inscription de Clerk.

Ce tutoriel utilise le motif recommandé : vérifier l'adresse depuis un endpoint serveur avant d'appeler signUp.create de Clerk.

Prérequis

  • Un compte Clerk et un projet Next.js avec @clerk/nextjs
  • Une clé d'API Mailbeam (inscription gratuite)

Mise en place

npm install svix  # svix est la bibliothèque de webhooks de Clerk

Endpoint de vérification côté serveur

// app/api/verify-email/route.ts
import { NextResponse } from "next/server";
// Pas encore de SDK : voici le wrapper de 12 lignes 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: "email required" }, { status: 400 });
  }

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

    return NextResponse.json({ valid, score, reason });
  } catch {
    // Échec permissif
    return NextResponse.json({ valid: true, score: 50, reason: null });
  }
}

Formulaire d'inscription sur mesure

// 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);

    // Étape 1 : vérifier l'adresse côté serveur avant d'appeler 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"
          ? "Merci d'utiliser une adresse e-mail permanente."
          : "Merci de fournir une adresse e-mail valide."
      );
      setLoading(false);
      return;
    }

    // Étape 2 : poursuivre l'inscription 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 ?? "L'inscription a échoué.");
    } 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="Adresse e-mail"
        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="Mot de passe"
        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 ? "Création du compte…" : "Créer le compte"}
      </button>
    </form>
  );
}

Bonnes pratiques

  • Vérifiez avant d'appeler Clerk, pas après : cela évite les utilisateurs orphelins non vérifiés
  • Gardez l'endpoint de vérification léger : il ne doit qu'appeler Mailbeam et renvoyer le résultat
  • Servez-vous de reason pour donner des messages d'erreur précis

Prochaines étapes