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

Email Guard Bundle Laravel Package

emailsherlock/email-guard-bundle

Symfony bundle that blocks junk and disposable email addresses at form submit using local checks, before they reach your database. Optional EmailSherlock API key adds live MX and SMTP verification.

View on GitHub
Deep Wiki
Context7

Getting Started

Treat this package as a reference implementation rather than a drop-in dependency. The README's headline feature — a verify_email form option and VerifiedEmail constraint — is Symfony-specific; Flex auto-registers the bundle and there is no Laravel equivalent. The usable core lives in emailsherlock/email-guard-core-php, which the bundle wraps. So the first move is:

  1. Read the email-guard-spec repo first (linked in the README) — it defines what "invalid", "disposable", "risky", and "review" mean. Your block_on/review_on choices only make sense against that vocabulary.
  2. Install the core package (composer require emailsherlock/email-guard-core-php) and use this bundle's config schema (api_key, fail_open, timeout_ms, block_on, review_on) as a design template for your own config/email_guard.php.
  3. Wire a first use case into a single FormRequest for signup — a custom Rule that calls the core client and maps its result to block_on/review_on classes.

If you genuinely need the Symfony bundle, that implies a Symfony service in your stack; then follow the README install as-is and expose the guard to Laravel via a small HTTP boundary or shared validation service. Don't try to shoehorn config/bundles.php into a Laravel app.

Implementation Patterns

Wrap, don't inherit. The bundle's constraint/form-option model maps cleanly to a Laravel custom validation rule:

class VerifiedEmail implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $result = app(EmailGuard::class)->verify((string) $value);

        if ($result->isInvalid() || $result->isDisposable()) {
            $fail('This email address is not accepted.');
        }
        // 'review' outcomes: don't fail — log or flag instead
    }
}

Use it in a FormRequest alongside, not instead of, ['required', 'email', 'max:255']. The bundle's value is the extra layer Symfony's Email (and Laravel's email rule) miss — reserved TLDs, disposable domains, live MX.

Layer local checks first, API second. The README is explicit: syntax profile, reserved TLDs, and 73k+ disposable domains run locally with zero latency. Treat the API key as optional enrichment. This gives you three tiers: (a) local block → fail immediately, (b) local pass + no key → accept, (c) local pass + key → MX/SMTP probe. Only tier (c) has network cost.

Model fail-open explicitly in config. fail_open: true is the correct default for signup — a 500 from the API shouldn't strand real users. But fail-open is a business decision: if you're selling a trial that costs money, fail-closed on API timeout may be the right call. Make it a config knob (EMAIL_GUARD_FAIL_OPEN) and surface the state in your logs so you can measure how often it triggers.

Use review_on as a soft signal. Don't conflate block and review. block_on: ['invalid','disposable'] rejects at submit; review_on: ['risky'] should route to a moderation queue or a "verify your email" flow, not a hard error. In Laravel: write the user, set email_review_status = 'pending', and let a queued job re-verify with a longer timeout_ms or live SMTP probe.

Cache the verdict. The API result for a given normalized address doesn't change often. Cache by hash('sha256', strtolower($email)) for hours, and use Cache::remember around the API call — not around the local check (which is already free). This also protects you against rate limits.

Async-heavy workloads → queue. For bulk imports or admin backfills, dispatch a job per email (or batches of 100) and never call the API inline. Keep the synchronous path only for interactive form submit.

Gotchas and Tips

  • Flex magic does not exist in Laravel. composer require emailsherlock/email-guard-bundle and expecting it to "just work" will leave you with an unregistered Doctrine-less bundle. If you actually run Symfony, add EmailGuardBundle to config/bundles.php manually — Flex is a nice-to-have, not a guarantee.
  • default::EMAILGUARD_API_KEY is Symfony's env syntax. In Laravel, env('EMAILGUARD_API_KEY') in config/email_guard.php behaves differently — note the default:: prefix in the README means "null if unset", which maps to env(...) returning null when unset. Don't copy the YAML literally.
  • 800ms timeout is a synchronous tax. Every signup can add up to 800ms. On a high-traffic form, that's user-visible. Consider lowering the timeout (400–500ms) and letting failures fall open, or moving the live probe to a post-signup verification step.
  • fail_open: true hides outages. Local checks still run, so your logs look "fine" while the API is down. Emit a metric on every API failure so you notice; otherwise you silently lose the live MX signal.
  • review_on: [] in the README default is meaningful. With an empty review list, risky emails are neither blocked nor flagged — they pass. Decide explicitly what "risky" means for your product before shipping.
  • Normalization matters. Disposable lists and MX checks are case-insensitive and alias-aware. Lowercase plus strip the +tag for the cache key, but validate the original. Otherwise you'll cache-miss on every plus-address and blow through API quota.
  • Unicode / IDN addresses. Symfony's Email supports html5/strict/rfc modes; Laravel's email:rfc,dns is similarly configurable. The guard's syntax profile may diverge from what you're already allowing — test against your existing accepted addresses before rollout to avoid regressions.
  • Extension points differ by framework. The bundle exposes Symfony constraint validators and form type extensions for customization. In Laravel, your extension seam is the ValidationRule's constructor (inject the client, allow per-field block_on overrides) and a container binding for the client so you can swap to a fake in tests.
  • Test with a fake client. The core package should be trivially mockable (it's HTTP-based). Bind an interface in the container and swap in tests — otherwise your validation test suite becomes an integration test against a third-party API.
  • Changelog/release cadence is a signal. Last release 2026-06-19 with 0 stars and 0 dependents means this is early. Pin to a caret constraint, watch releases, and keep the core package (not the bundle) as your primary dependency so a Symfony-side breakage can't ripple into Laravel.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin