Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Mailboxvalidator Php Laravel Package

mailboxvalidator/mailboxvalidator-php

MailboxValidator for Laravel validates email addresses through the MailboxValidator API, helping identify invalid, disposable, role-based, catch-all, and risky emails to improve deliverability and reduce bounces.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a plain PHP SDK, not a Laravel package — there is no service provider, config file, or facade, and no auto-discovery. Installation and wiring are on you.

composer require mailboxvalidator/mailboxvalidator-php

Where to look first:

  1. The constructor — it takes your API key directly: new MailboxValidator\MailboxValidator($apiKey).
  2. The three public methods: ValidateEmail($email), IsDisposableEmail($email), and IsFreeEmail($email). ValidateEmail returns the full result set (syntax, MX, disposable, free, role, catch-all, credits remaining); the other two are narrower calls.
  3. The raw response shape before you write any code against it — dump one real response early. Field names such as email_address, status, is_free, is_disposable, is_role, is_catchall, credits, error_code, and error_message are what you'll build on, but confirm the exact casing and types you receive rather than trusting docs.

Wire it into the container in AppServiceProvider::register() or a small dedicated provider:

// config/services.php
'mailboxvalidator' => [
    'key' => env('MAILBOXVALIDATOR_API_KEY'),
],

// AppServiceProvider
$this->app->singleton(MailboxValidator::class, fn () => new \MailboxValidator\MailboxValidator(
    config('services.mailboxvalidator.key')
));

First use case: gate user registration. Block disposable addresses at signup by adding a rule to your RegisterRequest, keeping Laravel's built-in email rule in front of it so you never spend an API credit on obvious garbage.

public function rules(): array
{
    return [
        'email' => ['required', 'string', 'email', 'unique:users,email', new NotDisposable()],
    ];
}

Don't stop at the raw library call — the single highest-value thing you can do on day one is define a one-method interface (EmailVerifier::verify(string $email): EmailVerdict) that returns a small DTO. Every pattern below depends on that seam.

Implementation Patterns

Wrap, don't call the SDK directly. The library returns an untyped decoded response (object/array, string-typed booleans, no exceptions). Map it into a readonly DTO once, in one place:

final class EmailVerdict
{
    public function __construct(
        public string $email,
        public bool $deliverable,
        public bool $disposable,
        public bool $free,
        public bool $role,
        public bool $catchAll,
        public ?string $errorCode = null,
    ) {}
}

Once this exists, the rest of the app never touches the SDK, and you can swap the implementation for direct HTTP calls or a different provider without a refactor.

Validation rule for sync paths. A Rule object is the cleanest integration for registration/checkout forms. Decide the policy explicitly: block disposable + role, allow free (or require a work email for B2B), and treat catch-all as "risky" rather than "invalid" — many legitimate corporate domains are catch-all.

public function passes($attribute, $value): bool
{
    $verdict = app(EmailVerifier::class)->verify($value);

    if ($verdict->errorCode) {
        return true; // fail open on provider errors, or false to fail closed
    }

    return $verdict->deliverable && ! $verdict->disposable && ! $verdict->role;
}

Never call the API on a bulk import path. For CSV imports, list uploads, or re-verification campaigns, dispatch a job per address and persist the result:

class VerifyEmailJob implements ShouldQueue
{
    public int $tries = 3;
    public int $timeout = 30;

    public function __construct(public int $emailRecordId) {}

    public function handle(EmailVerifier $verifier): void
    {
        $record = EmailRecord::findOrFail($this->emailRecordId);
        $verdict = $verifier->verify($record->email);
        $record->update([...$verdict->toArray(), 'checked_at' => now()]);
    }
}

Add RateLimited middleware on the job (->middleware([new RateLimited('mailboxvalidator')])) with a named limiter in AppServiceProvider — this is how you stay under the plan limit and avoid hitting a burst of error_code responses.

Cache aggressively, keyed by normalized email. Validation results are stable for weeks; a re-check of a 30-day-old valid address is a wasted credit. Cache sha256(lower(trim($email))) for 30–90 days for deliverable results, and only 1–7 days for catch-all/unknown so a domain that fixes its DNS gets re-evaluated. A second, domain-level cache for MX/DNS results is worth it if you import from the same corporate domains repeatedly.

Store the verdict, not just the boolean. Persist the individual flags (is_disposable, is_role, is_catchall, checked_at, credits_remaining). You will want to retroactively loosen or tighten the policy without re-spending credits. The credits field in every response is a free monitoring signal — log it and alert when it trends toward zero.

Test against your interface, not the network. Bind a fake in TestCase::setUp() or use $this->mock(EmailVerifier::class) in feature tests, and keep one thin integration test (marked @group integration, skipped without a key) to catch response-shape drift.

Gotchas and Tips

  • Http::fake() will not work. The SDK issues raw cURL requests; it is not built on Guzzle or PSR-18, so Laravel's HTTP client faking can't intercept it. Mock your EmailVerifier contract instead. This is the single most common surprise when adding tests.
  • Every method call is a separate API call and a separate credit. Calling ValidateEmail, IsDisposableEmail, and IsFreeEmail for one signup costs three credits. Always use ValidateEmail alone.
  • status/deliverable false is an answer, not an exception. The library returns error_code and error_message inside the payload rather than throwing. Check error_code before reading any flag, otherwise a failed lookup reads as "disposable = false", silently letting bad emails through.
  • Types are strings. Booleans commonly come back as "true"/"false" or 0/1. Cast explicitly in your DTO or a filter_var(..., FILTER_VALIDATE_BOOLEAN); loose comparisons against true will bite you.
  • No timeout, no retry built in. The cURL call blocks. Never call it on a web request you care about latency for without a guard — run it in a job with $timeout set, and if you retry, be aware retries consume credits too. Retry only on transport/network errors, not on error_code responses that indicate the address itself is unusable.
  • Normalize before you spend. strtolower(trim($email)), and run idn_to_ascii() on the domain part so müller.de and its punycode form hash to the same cache key.
  • Catch-all and greylisting produce false negatives. Treat is_catchall as "send a confirmation email and see" rather than reject. Similarly, is_server_down is transient — don't permanently blacklist.
  • API key hygiene and config caching. Keep the key in .env only, and remember php artisan config:cache after changing it — a stale config cache with an empty key yields confusing error_code responses rather than a clear auth failure.
  • Fail-open vs fail-closed is a product decision, make it once. Default to fail-open (accept on provider error) for consumer signup; fail-closed for fraud-sensitive or paid-tier flows. Put the choice in config, not scattered if statements.
  • Maintenance risk. 11 stars, no dependents, single-maintainer SDK, thin surface. Pin the version in composer.json and lean on your interface seam so a swap to direct REST calls (/v1/validation/single/) is an afternoon, not a rewrite.
  • Consider whether you need it at all. For low-stakes signups, filter_var + a checked disposable-domain blocklist + a confirmation email covers most of the value at zero cost. This API earns its keep on lead-gen forms, B2B signups, and anywhere an undeliverable address has a real cost.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons