romanzipp/laravel-mailcheck
romanzipp/laravel-mailcheck integrates Mailcheck.ai with Laravel to detect disposable, temporary, and throwaway email addresses. It provides a simple validation rule so you can reject risky signups before they reach your database.
Install with Composer and let Laravel's package discovery wire up the service provider:
composer require romanzipp/laravel-mailcheck
Publish the config so you can point the wrapper at the MailCheck.ai API and set an optional API key:
php artisan vendor:publish --tag=mailcheck
This drops a config/mailcheck.php file. Populate the relevant env vars in .env (e.g. MAILCHECK_API_KEY) and confirm the base URL is https://api.mailcheck.ai unless you're proxying requests.
First use case — reject disposable addresses during registration. The package registers a disposable validation rule, so the fastest win is:
$request->validate([
'email' => ['required', 'string', 'email', 'disposable'],
]);
Look first at:
config/mailcheck.php — API key, base URL, and any timeout/cache options exposed by the wrapper.MailCheck service class (resolved from the container, usually also facadable) — this is where isDisposable() / isDomainDisposable() live.vendor/romanzipp/laravel-mailcheck/src for the rule class if you need custom messages or to extend behaviour.Inline validation on a FormRequest. The idiomatic Laravel usage is to add the rule to your existing email validation rather than creating a separate validator:
public function rules(): array
{
return [
'email' => ['required', 'email', 'max:255', 'disposable'],
];
}
public function messages(): array
{
return [
'email.disposable' => 'Please use a permanent email address.',
];
}
Domain-only checks. If you're validating a company website or a signup domain rather than a full email, call the service directly:
use RomanZipp\MailCheck\MailCheck;
public function store(MailCheck $mailCheck)
{
if ($mailCheck->isDomainDisposable($request->input('domain'))) {
return back()->withErrors(['domain' => 'Disposable domains are not allowed.']);
}
}
Wrap it in a dedicated service. For larger apps, hide the package behind your own DisposableEmailChecker interface so tests and future providers don't reach into the vendor namespace:
class DisposableEmailChecker
{
public function __construct(private MailCheck $mailCheck) {}
public function check(string $email): bool
{
return Cache::remember("disposable:{$email}", now()->addDay(), fn () => $this->mailCheck->isDisposable($email));
}
}
Bind the interface in a service provider and inject it wherever needed. This is also the seam you use to swap the implementation in tests.
Combine with complementary rules. Disposable is one signal. Pair it with email:rfc,dns (Laravel 10+) or your own MX lookup, and optionally a role-account check (admin@, info@) — but keep them as separate rules so error messages stay actionable.
Async / queued checks for high-volume flows. If you're screening large imports, queue the API call rather than blocking a request. The API call is per-email, so batch through a job and store results on the user/lead record.
Testing. Since the wrapper is just HTTP under the hood, use Http::fake() in your test suite:
Http::fake([
'api.mailcheck.ai/*' => Http::response(['disposable' => true], 200),
]);
$this->post('/register', ['email' => '[email protected]'])
->assertSessionHasErrors('email');
For anything you control, prefer binding a fake DisposableEmailChecker in the container so tests don't depend on the vendor's response shape.
disposable rule calls the MailCheck.ai API synchronously. On a hot registration endpoint this adds latency and couples your signup flow to a third-party service. Cache results (per email or per domain) and consider a short TTL — the disposable-domain list changes slowly.timeout key, or bind a custom Guzzle client.disposable is not a substitute for email. Always keep the base email rule first. The package assumes a well-formed address; garbage input may yield surprising API responses.MAILCHECK_API_KEY and back off via a queued job rather than retrying inline.php artisan config:clear in development — a stale cached config with an empty API URL is a common silent failure.php artisan tinker and calling app(MailCheck::class)->isDisposable('[email protected]') is the quickest way to isolate whether a failure is in your rule setup or the API call. Log the raw response when status is unexpected.composer update.[email protected] and [email protected] return the same verdict. If you need finer-grained blocking (specific aliases, catch-all detection), that's your own layer on top.composer.json and skim the source before upgrading. If you need long-term stability, consider calling the MailCheck.ai API directly with Laravel's Http client — the surface area here is small enough that the wrapper may not earn its keep.How can I help you explore Laravel packages today?