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

Mailbeam
Vue 3 + Composition APIIntermédiaire20 minutesMis à jour en janvier 2025

Vérification d'e-mails dans Vue 3

Ce tutoriel construit un composable useEmailVerification réutilisable pour la Composition API de Vue 3. Il donne un retour immédiat à l'utilisateur pendant la saisie — valide, invalide, ou « vouliez-vous dire gmail.com ? » — tout en gardant votre clé d'API secrète bien au chaud sur le serveur.

Ce que vous allez construire

  • Un petit endpoint serveur qui relaie la vérification (pour que la clé d'API n'atteigne jamais le navigateur)
  • Un composable useEmailVerification temporisé qui renvoie un état réactif
  • Un formulaire d'inscription avec état en ligne, suggestion de correction et revérification à l'envoi

Prérequis

  • Vue 3 (Vite ou Nuxt) avec TypeScript
  • Une route back-end que vous contrôlez (Node, Python, etc.) pour le relais
  • Une clé d'API Mailbeam (inscription gratuite)

Étape 1 — Ajouter un endpoint de vérification côté serveur

N'appelez jamais Mailbeam directement depuis le navigateur : cela exposerait votre clé. Ajoutez un relais léger sur votre back-end. Le voici en Node/Express :

// server/routes/verify-email.js
// Pas encore de SDK : voici le wrapper de 12 lignes 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: "email required" });

  try {
    const { valid, score, reason, didYouMean } = await verifyEmail(email);
    res.json({ acceptable: valid && score >= 60, reason, didYouMean });
  } catch (err) {
    // Échec permissif : ne bloquez pas l'interface sur une erreur d'API
    res.json({ acceptable: true, reason: null, didYouMean: null });
  }
}

Étape 2 — Créer le 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"; // échec permissif dans l'interface
      reason.value = null;
    }
  }

  // Temporisation : on attend 500 ms après la dernière frappe
  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 };
}

Étape 3 — Le brancher sur un formulaire

<!-- 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() {
  // Revérification côté serveur à l'envoi — ne faites jamais confiance au seul état 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) {
    // remonter l'erreur de validation du serveur
  }
}

function applySuggestion() {
  if (suggestion.value) email.value = suggestion.value;
}
</script>

<template>
  <form @submit.prevent="onSubmit">
    <label for="email">Adresse e-mail</label>
    <input id="email" v-model="email" type="email" autocomplete="email" />

    <p v-if="status === 'checking'" class="hint">Vérification…</p>
    <p v-else-if="status === 'valid'" class="hint ok">✓ Adresse correcte</p>
    <p v-else-if="status === 'invalid'" class="hint err">
      Cette adresse semble non distribuable ({{ reason }}).
    </p>

    <p v-if="suggestion" class="hint">
      Vouliez-vous dire
      <button type="button" @click="applySuggestion">{{ suggestion }}</button> ?
    </p>

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

    <button type="submit" :disabled="!canSubmit">Créer le compte</button>
  </form>
</template>

Étape 4 — Revérifier systématiquement sur le serveur

Le composable améliore l'expérience, mais l'état client peut être falsifié ou contourné. Votre handler /api/signup doit refaire la même vérification avant de créer l'utilisateur — réutilisez la logique de relais de l'étape 1.

Bonnes pratiques

PratiquePourquoi
Temporiser à ~500 msUn appel par pause, pas un par touche
Annuler les requêtes en volÉvite les résultats hors séquence pendant la saisie
Passer par un relais sur votre serveurGarde la clé d'API hors du client
Revérifier à l'envoiL'état client est indicatif, pas faisant foi
Exposer didYouMeanRattrape les fautes de frappe du type gmial.com

Liste de contrôle avant la mise en production

  • L'endpoint de vérification est limité en débit par IP
  • MAILBEAM_KEY n'existe que côté serveur
  • Le handler d'envoi revérifie côté serveur
  • L'interface échoue en mode permissif si le contrôle plante
  • Les états de chargement et d'erreur sont accessibles (aria-live)

Prochaines étapes