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

Mailbeam
Laravel + PHPDébutant15 minutesMis à jour en janvier 2025

Vérification d'e-mails dans Laravel

Ce tutoriel intègre Mailbeam à une application Laravel sous la forme d'une règle de validation maison. La règle s'insère dans le système de validation existant de Laravel : vous pouvez donc l'employer partout où vous écririez required ou email.

Ce que vous allez construire

  • Une règle de validation ValidMailbeamEmail (Laravel 10+)
  • Une couche de cache facultative, via la façade Cache de Laravel
  • Une Form Request SignupRequest qui utilise la règle
  • Des tests PHPUnit

Prérequis


Étape 1 — Configurer la clé d'API

Ajoutez ceci à config/services.php :

'mailbeam' => [
    'key' => env('MAILBEAM_KEY'),
    'base_url' => 'https://api.mailbeam.dev',
],

Et ceci à .env :

MAILBEAM_KEY=mb_live_xxxxxxxxxxxxxxxxxxxx

Étape 2 — Créer la règle de validation

php artisan make:rule ValidMailbeamEmail
<?php
// app/Rules/ValidMailbeamEmail.php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class ValidMailbeamEmail implements ValidationRule
{
    public function __construct(
        private int $minScore = 60,
        private bool $failOpen = true
    ) {}

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $email = strtolower(trim((string) $value));
        $cacheKey = 'mailbeam_' . md5($email);

        // On consulte d'abord le cache
        $result = Cache::remember($cacheKey, now()->addHour(), function () use ($email) {
            return $this->callMailbeam($email);
        });

        if ($result === null) {
            // Erreur d'API — échec permissif (on ne rejette pas l'adresse)
            return;
        }

        $valid = $result['valid'] ?? false;
        $score = $result['score'] ?? 0;
        $reason = $result['reason'] ?? null;

        if (!$valid || $score < $this->minScore) {
            $message = $this->getErrorMessage($reason);
            $fail($message);
        }
    }

    private function callMailbeam(string $email): ?array
    {
        try {
            $response = Http::withToken(config('services.mailbeam.key'))
                ->timeout(5)
                ->post(config('services.mailbeam.base_url') . '/v1/verify', [
                    'email' => $email,
                ]);

            if ($response->successful()) {
                return $response->json();
            }

            Log::warning('Erreur de l\'API Mailbeam', [
                'status' => $response->status(),
                'body' => $response->body(),
            ]);

            return $this->failOpen ? null : ['valid' => false, 'score' => 0, 'reason' => 'api_error'];

        } catch (\Throwable $e) {
            Log::error('Échec de la requête Mailbeam', ['error' => $e->getMessage()]);
            return $this->failOpen ? null : ['valid' => false, 'score' => 0, 'reason' => 'api_error'];
        }
    }

    private function getErrorMessage(?string $reason): string
    {
        return match ($reason) {
            'disposable_domain' => 'Merci d\'utiliser une adresse e-mail permanente, pas une adresse temporaire.',
            'no_mx_records'     => 'Ce domaine ne semble pas accepter de courrier.',
            'smtp_rejected'     => 'Cette adresse e-mail ne semble pas exister.',
            'role_address'      => 'Merci d\'utiliser une adresse e-mail personnelle.',
            default             => 'Merci de fournir une adresse e-mail valide et joignable.',
        };
    }
}

Étape 3 — Créer une Form Request

php artisan make:request SignupRequest
<?php
// app/Http/Requests/SignupRequest.php

namespace App\Http\Requests;

use App\Rules\ValidMailbeamEmail;
use Illuminate\Foundation\Http\FormRequest;

class SignupRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'email'                 => ['required', 'email:rfc,dns', new ValidMailbeamEmail()],
            'password'              => ['required', 'string', 'min:8', 'confirmed'],
            'password_confirmation' => ['required', 'string'],
        ];
    }

    public function messages(): array
    {
        return [
            'email.email' => 'Merci de saisir une adresse e-mail au format valide.',
        ];
    }
}

Étape 4 — L'utiliser dans votre contrôleur

<?php
// app/Http/Controllers/Auth/SignupController.php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Http\Requests\SignupRequest;
use App\Models\User;
use Illuminate\Support\Facades\Hash;

class SignupController extends Controller
{
    public function store(SignupRequest $request)
    {
        // $request->validated() ne s'exécute qu'une fois toutes les règles passées
        $validated = $request->validated();

        $user = User::create([
            'email'    => $validated['email'],
            'password' => Hash::make($validated['password']),
        ]);

        auth()->login($user);

        return redirect('/dashboard');
    }
}

La route :

// routes/web.php
Route::post('/signup', [SignupController::class, 'store'])->name('signup');

Tester

<?php
// tests/Feature/SignupTest.php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Support\Facades\Http;
use Illuminate\Foundation\Testing\RefreshDatabase;

class SignupTest extends TestCase
{
    use RefreshDatabase;

    public function test_valid_email_creates_account(): void
    {
        Http::fake([
            'api.mailbeam.dev/*' => Http::response([
                'valid' => true, 'score' => 94, 'reason' => null,
            ], 200),
        ]);

        $response = $this->post('/signup', [
            'email'                 => 'user@example.com',
            'password'              => 'secret1234',
            'password_confirmation' => 'secret1234',
        ]);

        $response->assertRedirect('/dashboard');
        $this->assertDatabaseHas('users', ['email' => 'user@example.com']);
    }

    public function test_disposable_email_is_rejected(): void
    {
        Http::fake([
            'api.mailbeam.dev/*' => Http::response([
                'valid' => false, 'score' => 2, 'reason' => 'disposable_domain',
            ], 200),
        ]);

        $response = $this->post('/signup', [
            'email'                 => 'temp@mailinator.com',
            'password'              => 'secret1234',
            'password_confirmation' => 'secret1234',
        ]);

        $response->assertSessionHasErrors('email');
        $this->assertDatabaseMissing('users', ['email' => 'temp@mailinator.com']);
    }

    public function test_api_failure_does_not_block_signup(): void
    {
        Http::fake([
            'api.mailbeam.dev/*' => Http::response([], 500),
        ]);

        // Avec failOpen=true (le défaut), un 500 de Mailbeam ne doit pas bloquer l'inscription
        $response = $this->post('/signup', [
            'email'                 => 'user@example.com',
            'password'              => 'secret1234',
            'password_confirmation' => 'secret1234',
        ]);

        $response->assertRedirect('/dashboard');
    }
}

Liste de contrôle avant la mise en production

  • MAILBEAM_KEY présente dans .env et définie sur le serveur de production
  • php artisan config:cache exécuté après l'ajout dans config/services.php
  • Pilote de cache configuré (CACHE_DRIVER=redis recommandé en production)
  • failOpen=true (le défaut) relu — le compromis est compris
  • Messages d'erreur traduits si votre application est multilingue

Prochaines étapes