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

Laravel Hunter Laravel Package

jeffersongoncalves/laravel-hunter

Laravel Hunter by Jefferson Goncalves is a lightweight diagnostic package for Laravel applications. It helps you hunt down hidden issues, inspect problem areas, and keep your codebase clean with developer-friendly tooling built to speed up debugging and maintenance.

View on GitHub
Deep Wiki
Context7

Getting Started

This is the initial 1.0.0 release, so treat the public API as stable-but-young: pin ^1.0 in composer.json and read the changelog on every minor bump until the package proves itself over a few releases.

Install and wire up (5 minutes):

composer require <vendor>/laravel-hunter   # exact vendor name from Packagist
php artisan vendor:publish --tag="hunter-config"

Add the key to .env (never hard-code it):

HUNTER_API_KEY=your_key_here

Then clear config cache: php artisan config:clear.

Where to look first, in order:

  1. config/hunter.php — base URL, timeout, retry settings, and the api_key binding.
  2. The facade / service provider registration — confirms the container alias (Hunter::) and whether an interface is bound for swapping.
  3. The response/DTO layer — tells you whether you get arrays, Collections, or typed objects back. This determines how much defensive coding you need downstream.
  4. The exception classes — the single most important thing to know before writing your first call site.

First real use case: verifying an email at the point of capture. In a form request or registration listener:

use Hunter; // or the injected contract

$result = Hunter::emailVerifier($request->email);

if ($result->status === 'invalid') {
    throw ValidationException::withMessages([
        'email' => 'That email address does not appear to be deliverable.',
    ]);
}

This is the highest-value, lowest-risk integration: one call, one decision, clearly bounded. Do this before you touch leads or campaigns — the campaign endpoints have far more surface area and cost.

Implementation Patterns

Prefer constructor injection over the facade. Even if the package ships a facade, resolve the underlying client through the container so you can swap it in tests:

public function __construct(private \Vendor\Hunter\HunterClient $hunter) {}

If the package binds an interface, bind it to a fake in a test service provider instead of hitting Http::fake() everywhere.

Wrap the client in a thin application-level service. Do not let Hunter's response shape leak into your controllers. Three reasons: (a) you may switch providers later, (b) you want one place to normalise null vs not_found vs exception, (c) you want one place to attach logging and credit accounting.

class EmailEnrichment
{
    public function __construct(private HunterClient $hunter) {}

    public function verify(string $email): VerificationOutcome
    {
        $response = $this->hunter->emailVerifier($email);

        return new VerificationOutcome(
            status: $response->status ?? 'unknown',
            score:  $response->score ?? 0,
            raw:    $response->toArray(),
        );
    }
}

Cache aggressively — this is a paid, credit-metered API. Account info barely changes; verifier results are stable for weeks. A 24-hour cache on account info and a 7–30 day cache on verifier results will pay for itself immediately:

return Cache::remember("hunter:verify:{$email}", now()->addDays(14), fn () => ...);

Bulk work belongs in queued jobs with an explicit rate limiter. Domain search and email finder are the calls you'll want to fan out over a prospect list. Each job should be individually retryable and individually rate-limited:

// AppServiceProvider::boot()
RateLimiter::for('hunter', fn () => Limit::perMinute(10));

// job
public function handle(): void
{
    $result = $this->hunter->emailFinder($this->domain, $this->first, $this->last);
    ...
}

Set $tries = 3, $backoff = [10, 60, 300], and make the job idempotent — a retried email finder that already succeeded server-side will still burn a credit on the retry unless you short-circuit on cached/persisted results. Check your own database first.

Pagination on leads and campaigns. These listings are windowed — Hunter caps how far back you can page. Build your sync to checkpoint on a stored cursor or created_at watermark rather than blindly incrementing offset, otherwise a full re-sync will silently miss records once you exceed the window.

Integration with Eloquent: store a normalised record per contact (hunter_status, hunter_score, hunter_checked_at) rather than re-querying. A small HasHunterVerification trait with a refreshVerification() method keeps it tidy and makes the API call site obvious in code review.

Feature-test everything. Never let a test suite reach the real API — one CI run over a fixture set can drain a month of credits. If the package ships a fake, use it; if not, Http::fake() on the base URL and assert on the outgoing request body.

Gotchas and Tips

  • Credits are consumed on success, not on intent. A 404/not found from email finder can still count against your plan. Never fire speculative calls in a loop, in a page load, or in an observer that runs on every model save.
  • "Not found" is a normal outcome, not an error. Hunter often returns a 200 with an empty result or a null in the data payload. Code that only branches on exceptions will crash on $result->emails[0]. Always null-check the inner payload before indexing.
  • The verifier returns unknown frequently. Disposable domains, catch-all servers, and greylisting all produce unknown with a low score. Decide your policy up front — usually: valid → accept, invalid → reject, unknown → accept but flag for re-check later. Treating unknown as invalid will reject real customers.
  • env() outside config/ is a trap. If you find yourself writing env('HUNTER_API_KEY') in a service class, stop — it returns null the moment anyone runs php artisan config:cache. Always go through config('hunter.api_key').
  • 429s need backoff, not retries-in-place. The package's retry setting (if present) may retry immediately. For bulk jobs, catch the rate-limit exception and release the job with a delay ($this->release(60)) rather than letting the HTTP layer hammer it.
  • Timeouts default to something generous. Set an explicit connect/request timeout in config (e.g. 5s / 15s). A synchronous verify call in a registration flow that hangs for 30 seconds will exhaust PHP-FPM workers under load.
  • Test for the "package returns an unexpected shape" failure. Since this is a 1.0.0, the DTO may not yet cover every field Hunter returns. If you cast responses to typed objects, an added field is fine but a missing one can throw. Prefer reading with ?? defaults in your adapter layer.
  • API keys have scopes and account-level limits. A key provisioned for one Hunter account may have campaigns disabled. account() is the cheapest way to confirm what the key can actually do — call it once on deploy and log the result.
  • Leads and campaigns are write operations with real-world consequences. A misconfigured campaign can send email to real prospects. Gate any campaign-creation code behind an explicit environment check and a confirmation step; never let it run from a scheduled task in staging with production keys.
  • Compliance is on you, not the package. Leads data contains personal information. If you persist it, you need a retention policy and a lawful basis under GDPR. Check what the package logs by default — API keys appearing in a debug log or a failed-request exception trace is a common and serious leak. Scrub api_key from any Http::beforeSending logging.
  • Extension points worth knowing: the HTTP client is usually injectable (Laravel's PendingRequest or a Guzzle instance), which lets you add a custom User-Agent, a trace header, or a middleware that logs request IDs. If the client is not injectable, you're limited to the config file — note that as a limitation when you evaluate the 1.1 release.
  • Version-bump hygiene: for a first release, subscribe to the repo's releases feed. A 1.0.x patch should be safe; a 1.1.0 may rename response properties or change default config keys, and config/hunter.php in your app will not be updated by composer update — you'll need to re-publish and diff it manually.
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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums