kolaybi / mail-checker
A Laravel package providing e-mail validation for better email delivery.
Requires
- php: ^8.4
- illuminate/console: ^12 || ^13
- illuminate/database: ^12 || ^13
- illuminate/support: ^12 || ^13
Requires (Dev)
- laravel/pint: ^1.27
- orchestra/testbench: ^10.9
- pestphp/pest: ^4.4
- pestphp/pest-plugin-laravel: ^4.1
This package is auto-updated.
Last update: 2026-08-05 12:30:38 UTC
README
A Laravel package providing comprehensive e-mail validation for better email delivery.
Features
- Validate email format and structure
- Check against disposable email domains
- Blacklist and whitelist domain support
- Integration with external validation services
- Flexible configuration
- Detailed exception handling
Installation
You can install the package via composer:
composer require kolaybi/mail-checker
Configuration
Publish the configuration file:
php artisan vendor:publish --provider="KolayBi\Validation\Mail\ServiceProvider"
This will create a mail-checker.php configuration file in your application's config directory.
Environment Variables
Configure the following environment variables in your .env file:
# Local domain lists paths MAIL_CHECKER_WHITELIST_STORAGE_PATH=data/domains/whitelisted_domains.json MAIL_CHECKER_BLACKLIST_STORAGE_PATH=data/domains/blacklisted_domains.json MAIL_CHECKER_DISPOSABLE_STORAGE_PATH=data/domains/disposable_domains.json MAIL_CHECKER_DISPOSABLE_URL=https://rawgit.com/andreis/disposable-email-domains/master/domains.json # Cache configuration MAIL_CHECKER_LOCAL_CACHE_ENABLED=true MAIL_CHECKER_LOCAL_CACHE_TTL=604800 MAIL_CHECKER_LOCAL_CACHE_STORE=file MAIL_CHECKER_EXTERNAL_CACHE_ENABLED=true MAIL_CHECKER_EXTERNAL_CACHE_TTL=86400 MAIL_CHECKER_EXTERNAL_CACHE_STORE=file # External provider configuration MAIL_CHECKER_FAIL_IF_NO_PROVIDERS=true MAIL_CHECKER_EXTERNAL_PROVIDER_PRIORITY=abstract_api,mailboxlayer,mailgun MAIL_CHECKER_EXTERNAL_TIMEOUT=10 # AbstractAPI configuration ABSTRACT_API_EMAIL_ENDPOINT=https://emailvalidation.abstractapi.com/v1/ ABSTRACT_API_EMAIL_API_KEY=your_api_key ABSTRACT_API_EMAIL_TIMEOUT=10 # Bouncer configuration BOUNCER_ENDPOINT=https://api.usebouncer.com/v1.1/email/verify BOUNCER_API_KEY=your_api_key BOUNCER_TIMEOUT=10 # Emailable configuration EMAILABLE_ENDPOINT=https://api.emailable.com/v1/verify EMAILABLE_API_KEY=your_api_key EMAILABLE_TIMEOUT=10 # Hunter configuration HUNTER_VALIDATION_ENDPOINT=https://api.hunter.io/v2/email-verifier HUNTER_API_KEY=your_api_key HUNTER_TIMEOUT=10 # Kickbox configuration KICKBOX_ENDPOINT=https://api.kickbox.com/v2/verify KICKBOX_API_KEY=your_api_key KICKBOX_TIMEOUT=10 # MailboxLayer configuration MAILBOX_LAYER_ENDPOINT=https://apilayer.net/api/check MAILBOX_LAYER_ACCESS_KEY=your_access_key MAILBOX_LAYER_TIMEOUT=10 # Mailgun configuration MAILGUN_VALIDATION_ENDPOINT=https://api.mailgun.net/v4/address/validate MAILGUN_API_KEY=your_api_key MAILGUN_TIMEOUT=10 # NeverBounce configuration NEVER_BOUNCE_VALIDATION_ENDPOINT=https://api.neverbounce.com/v4/single/check NEVER_BOUNCE_API_KEY=your_api_key NEVER_BOUNCE_TIMEOUT=10 # Suppression list configuration (opt-in, see "Suppression list" below) MAIL_CHECKER_SUPPRESSION_ENABLED=false MAIL_CHECKER_SUPPRESSION_DB_CONNECTION= MAIL_CHECKER_SUPPRESSION_DB_SCHEMA=
Usage
Basic Validation
use KolayBi\Validation\Mail\MailChecker; use KolayBi\Validation\Mail\Exceptions\AbstractMailException; try { MailChecker::check('user@example.com'); // Email is valid } catch (AbstractMailException $e) { // Handle specific validation errors echo $e->getMessage(); }
Simplified Boolean Check
if (MailChecker::isValid('user@example.com')) { // Email is valid } else { // Email is invalid }
Skip External Validation
// Perform only local validation checks MailChecker::check('user@example.com', skipExternalControl: true);
// Perform only local validation checks if (MailChecker::isValid('user@example.com', skipExternalControl: true)) { // Email is valid } else { // Email is invalid }
Advanced Validation Methods
The package provides granular validation methods for specific checks:
// Check individual validation aspects if (MailChecker::isWhitelisted('user@example.com')) { // Email domain is in whitelist - trusted domain } if (MailChecker::isBlacklisted('user@example.com')) { // Email domain is explicitly blocked } if (MailChecker::isDisposable('user@example.com')) { // Email is from a temporary/disposable email provider } if (MailChecker::isValidFormat('user@example.com')) { // Email format passes validation checks } // Inverse methods for negative checks if (MailChecker::isNotWhitelisted('user@example.com')) { // Email requires validation (not in trusted whitelist) } if (MailChecker::isNotBlacklisted('user@example.com')) { // Email is not explicitly blocked } if (MailChecker::isNotDisposable('user@example.com')) { // Email is from a permanent email provider } if (MailChecker::isInvalidFormat('user@example.com')) { // Email format is invalid }
Exception Types
The package throws specific exceptions for different validation scenarios:
EmptyMailException- Email address is emptyInvalidMailException- Email format is invalidBlacklistedMailException- Domain is blacklistedDisposableMailException- Domain is from a disposable email providerInaccessibleMailException- Failed external validationExternalMailProviderException- External validation error
Command Line Interface
Manage domain lists using artisan commands:
# Update disposable domain list from configured URL php artisan mail-checker:update-disposable-domains # Whitelist operations php artisan mail-checker:update-domains --type=whitelist --add=kolaybi.com --add=newdomain.com php artisan mail-checker:update-domains --type=whitelist --remove=oldomain.com php artisan mail-checker:update-domains --type=whitelist --list php artisan mail-checker:update-domains --type=whitelist --add=kolaybi.com --add=newdomain.com --remove=oldomain.com --list # Blacklist operations php artisan mail-checker:update-domains --type=blacklist --add=spam.com php artisan mail-checker:update-domains --type=blacklist --remove=notspam.com php artisan mail-checker:update-domains --type=blacklist --list php artisan mail-checker:update-domains --type=blacklist --add=spam.com --add=fraud.com --remove=notspam.com --list # Cache management php artisan mail-checker:cache-clear # Clear all caches php artisan mail-checker:cache-clear --type=local # Clear only local validation cache php artisan mail-checker:cache-clear --type=external # Clear only external validation cache php artisan mail-checker:cache-clear --type=local --domain-type=whitelist # Clear only whitelist domain cache
Suppression list
An opt-in suppression list for addresses that bounced, complained, or were manually
flagged, so they are never sent to again. It is disabled by default
(enabled = false) — until you opt in, the package behaves exactly as before with
zero change in validation behavior.
Configuration
'suppression' => [ 'enabled' => (bool) env('MAIL_CHECKER_SUPPRESSION_ENABLED', false), 'connection' => env('MAIL_CHECKER_SUPPRESSION_DB_CONNECTION'), 'schema' => env('MAIL_CHECKER_SUPPRESSION_DB_SCHEMA'), 'table' => 'mail_suppressions', 'model' => SuppressedEmail::class, ],
enabled(MAIL_CHECKER_SUPPRESSION_ENABLED) — must be explicitly set totrueto turn the feature on. Defaults tofalse.connection(MAIL_CHECKER_SUPPRESSION_DB_CONNECTION) — database connection for the suppression table. Defaults tonull(the application's default connection).schema(MAIL_CHECKER_SUPPRESSION_DB_SCHEMA) — optional schema/database prefix applied to the table name (schema.table). Defaults tonull(no prefix).table— suppression table name. Not env-backed; override the published config directly. Defaults tomail_suppressions.model— the Eloquent model class used for suppression queries. Not env-backed; override the published config directly to substitute your own model. Defaults toKolayBi\Validation\Mail\Models\SuppressedEmail.
Publish and run the migration to create the table:
php artisan vendor:publish --provider="KolayBi\Validation\Mail\ServiceProvider" --tag=mail-checker-migrations
php artisan migrate
Static API
use KolayBi\Validation\Mail\MailChecker; use KolayBi\Validation\Mail\Enums\SuppressionReason; MailChecker::isSuppressed(string $mail): bool; MailChecker::isNotSuppressed(string $mail): bool; MailChecker::suppress( string $mail, SuppressionReason $reason, ?string $source = null, ?CarbonInterface $suppressedAt = null, array $metadata = [], ): SuppressedEmail; MailChecker::unsuppress(string $mail): bool;
suppress() and unsuppress() throw a RuntimeException while the feature is
disabled; read-only checks (isSuppressed() / isNotSuppressed()) simply return
false/true without touching the database.
When enabled, MailChecker::check() (and therefore isValid()) throws
SuppressedMailException for a suppressed address, checked after the
blacklist/disposable checks and before external deliverability validation.
Console commands
# Suppress an address php artisan mail-checker:suppress user@example.com --reason=bounce --source=webhook # Remove an address from the suppression list php artisan mail-checker:unsuppress user@example.com # Bulk import from a CSV file php artisan mail-checker:suppression-import path/to/suppressions.csv --source=import
--reason accepts bounce, complaint or manual (case-insensitive) and defaults
to manual.
mail-checker:suppression-import reads a CSV with header email,reason,suppressed_at:
email,reason,suppressed_at bounced@example.com,bounce,2026-01-01 00:00:00 complained@example.com,COMPLAINT,
reasonisbounce,complaintormanual, case-insensitive.suppressed_atmay be left empty — it defaults to the time of import.- Rows with a blank email or an unrecognized reason are skipped and reported separately from the imported count.
Sync interface (upstream/ESP suppression state)
To keep an external ESP's suppression state in sync with unsuppress(), bind an
implementation of SuppressionSyncInterface in your application container:
namespace KolayBi\Validation\Mail\Contracts; interface SuppressionSyncInterface { /** * Remove the address from the upstream (ESP-side) suppression state. * Implementations MUST throw on failure so unsuppress fails closed. */ public function forget(string $email): void; }
unsuppress()callsforget()before deleting the local record.- Implementations must throw on failure.
unsuppress()does not catch that exception, so a failed upstream sync aborts the operation and leaves the local suppression record in place — it fails closed instead of silently unsuppressing locally while the ESP still blocks the address. - If no implementation is bound,
unsuppress()only affects the local suppression list. - The package does not bind a default implementation — consuming applications are
responsible for binding
SuppressionSyncInterfacein their own container (for example in a service provider'sregister()).
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
License
Please see License File for more information.