Beta privataMailbeam è in beta privata: l'API pubblica non è ancora aperta.Iscriviti alla lista d'attesa

Mailbeam
Laravel + PHPBase15 minutiAggiornato a gennaio 2025

Verifica delle email in Laravel

Questo tutorial integra Mailbeam in un'applicazione Laravel come una regola di validazione tua. La regola si aggancia al sistema di validazione che Laravel ha già, quindi puoi usarla ovunque useresti required o email.

Che cosa costruirai

  • Una regola di validazione ValidMailbeamEmail (Laravel 10+)
  • Uno strato di cache facoltativo con la facade Cache di Laravel
  • Una Form Request SignupRequest che usa la regola
  • Test con PHPUnit

Prerequisiti


Passo 1 — Configura la chiave API

Aggiungi questo a config/services.php:

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

E questo a .env:

MAILBEAM_KEY=mb_live_xxxxxxxxxxxxxxxxxxxx

Passo 2 — Crea la regola di validazione

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

        // Guarda prima nella cache
        $result = Cache::remember($cacheKey, now()->addHour(), function () use ($email) {
            return $this->callMailbeam($email);
        });

        if ($result === null) {
            // Errore dell'API: lascia passare (non rifiutare l'email)
            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('Errore dell\'API di Mailbeam', [
                'status' => $response->status(),
                'body' => $response->body(),
            ]);

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

        } catch (\Throwable $e) {
            Log::error('La richiesta a Mailbeam è fallita', ['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' => 'Usa un indirizzo email permanente, non uno temporaneo.',
            'no_mx_records'     => 'Questo dominio non sembra accettare posta.',
            'smtp_rejected'     => 'Questo indirizzo email non sembra esistere.',
            'role_address'      => 'Usa un indirizzo email personale.',
            default             => 'Inserisci un indirizzo email valido e raggiungibile.',
        };
    }
}

Passo 3 — Crea una 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' => 'Inserisci un\'email in un formato valido.',
        ];
    }
}

Passo 4 — Usala nel tuo controller

<?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() viene eseguito solo quando tutte le regole passano
        $validated = $request->validated();

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

        auth()->login($user);

        return redirect('/dashboard');
    }
}

Rotta:

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

Prove

<?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),
        ]);

        // Con failOpen=true (predefinito), un 500 di Mailbeam non deve bloccare la registrazione
        $response = $this->post('/signup', [
            'email'                 => 'user@example.com',
            'password'              => 'secret1234',
            'password_confirmation' => 'secret1234',
        ]);

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

Lista di controllo per la produzione

  • MAILBEAM_KEY in .env e definita sul server di produzione
  • php artisan config:cache eseguito dopo averla aggiunta a config/services.php
  • Driver di cache configurato (CACHE_DRIVER=redis consigliato in produzione)
  • failOpen=true (predefinito) rivisto: capisci il compromesso
  • Messaggi di errore tradotti se la tua applicazione è multilingue

Prossimi passi