kolaybi/mail-checker

A Laravel package providing e-mail validation for better email delivery.

Maintainers

Package info

github.com/kolaybi/mail-checker

pkg:composer/kolaybi/mail-checker

Transparency log

Statistics

Installs: 523

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

2.2.0 2026-08-05 12:29 UTC

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

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 empty
  • InvalidMailException - Email format is invalid
  • BlacklistedMailException - Domain is blacklisted
  • DisposableMailException - Domain is from a disposable email provider
  • InaccessibleMailException - Failed external validation
  • ExternalMailProviderException - 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 to true to turn the feature on. Defaults to false.
  • connection (MAIL_CHECKER_SUPPRESSION_DB_CONNECTION) — database connection for the suppression table. Defaults to null (the application's default connection).
  • schema (MAIL_CHECKER_SUPPRESSION_DB_SCHEMA) — optional schema/database prefix applied to the table name (schema.table). Defaults to null (no prefix).
  • table — suppression table name. Not env-backed; override the published config directly. Defaults to mail_suppressions.
  • model — the Eloquent model class used for suppression queries. Not env-backed; override the published config directly to substitute your own model. Defaults to KolayBi\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,
  • reason is bounce, complaint or manual, case-insensitive.
  • suppressed_at may 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() calls forget() 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 SuppressionSyncInterface in their own container (for example in a service provider's register()).

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.