h-farm/laravel-email-domain-rule
A Laravel validation rule for checking the domain of an email address. Allow or block specific domains, filter disposable or corporate emails, and get clear validation messages—drop it into form requests or validators with minimal setup.
composer require maize-tech/laravel-email-domain-rule:^3.2
There is nothing to publish, migrate, or boot. The package is a single validation rule, so the first place to look is src/EmailDomainRule.php — it is small enough to read end to end in five minutes, and doing so tells you exactly which constructor arguments exist and how subdomain matching is decided. Do that before wiring it into a FormRequest; it is faster than guessing from the README.
Minimal working use case — gate a B2B signup form to company domains:
use Maize\EmailDomainRule\EmailDomainRule;
$request->validate([
'email' => [
'required',
'email',
new EmailDomainRule(['acme.com', 'acme.io']),
],
]);
Confirm the constructor shape against the README for the exact tag you install (the rule can be driven either as an allow-list or as a deny-list depending on how it is instantiated), then write one Validator::make() test asserting both the pass and the fail path before shipping it into a form.
Upgrading from 3.1.x: 3.2.0 is purely PHP 8.3 and Laravel 11 compatibility — no API changes, no deprecations. Bump the constraint, run the suite, move on.
Config-driven domain lists. Hardcoding domains in a FormRequest guarantees a deploy every time marketing adds a brand. Put the list in config instead:
// config/company.php
'email_domains' => array_filter(explode(',', env('ALLOWED_EMAIL_DOMAINS', 'acme.com'))),
Then a thin wrapper rule that composes the package rule keeps call sites clean and the list editable per environment:
final class CompanyEmail implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$inner = new EmailDomainRule(config('company.email_domains'));
// delegate, and re-word the failure for your product
}
}
Deny-list mode for consumer providers. The inverse shape — allow everything except gmail.com, outlook.com, proton.me, plus a disposable-domain list — is the more common production case for B2B products. Keep that list in config, not in code, and expect it to be edited quarterly.
Subdomains are the usual surprise. Enterprise customers sign up from mail.acme.com or a regional subdomain. Decide explicitly whether a suffix match is acceptable, and encode that decision once in your wrapper rather than sprinkling variations across form requests.
Testing. Assert behaviour, not the package internals, with a dataset:
it('accepts company domains', function (string $email) {
expect(Validator::make(['email' => $email], ['email' => ['required', 'email', new CompanyEmail]])->passes())->toBeTrue();
})->with(['[email protected]', '[email protected]']);
Layer with email:dns. The rule inspects the string only. If you also want to reject @acme.con typos, add Laravel's email:dns (or email:rfc,dns) alongside it — but keep it out of the test suite so tests stay offline.
Str::lower() in your wrapper before comparing; Acme.com from a mobile keyboard otherwise fails silently. The same applies to trailing dots and whitespace.null. A missing field skips non-implicit rules entirely, so always pair with required (or nullable where intended) rather than relying on the domain rule to catch empty input.email.* keys.@. If you write any derived logic (logging the rejected domain, say), use Str::afterLast($value, '@'); quoted local parts make the first @ unreliable.$request->validate([...], [...]) — the key is name-derived and brittle across versions.bail and ordering. With bail, an invalid-format email never reaches the domain check; without it, users can see two errors at once. Pick deliberately for your UX.xn--…) and Unicode forms are different strings. If you serve non-Latin markets, normalise to punycode before comparing — this is not handled for you.php artisan config:cache; remember to re-cache in deploy scripts or the list will appear stale.vendor:publish it explicitly rather than assuming it exists.ValidationRule and keep the dependency swappable.How can I help you explore Laravel packages today?