juanparati/laravel-kickbox
Laravel integration for the Kickbox email verification API. Validate addresses in real time, detect risky or undeliverable emails, and reduce bounces by wiring Kickbox checks directly into your Laravel app's validation and service layer.
juanparati/laravel-kickbox is a thin Laravel wrapper around the Kickbox email-verification API (send/verify). Release 1.2 shipped without detailed notes — the changelog entry is only a compare link — so treat this as a maintenance bump until you've reviewed the diff at https://github.com/juanparati/laravel-kickbox/compare/1.1...1.2.
composer require juanparati/laravel-kickbox
php artisan vendor:publish --provider="Juanparati\LaravelKickbox\KickboxServiceProvider"
Add your credentials to .env (the published config file is the source of truth for key names — check it rather than guessing):
KICKBOX_API_KEY=your_public_or_secret_key
Look first at:
config/kickbox.php — endpoint, API key, timeouts, and any default options.src/ for the request/response DTOs; Kickbox returns a result (deliverable, undeliverable, risky, unknown), a reason, and a role/free/disposable/accept_all flag set. Those fields drive every real decision you'll make.First use case: verify an address at registration before you persist the user.
use Juanparati\LaravelKickbox\Facades\Kickbox;
$response = Kickbox::verify($request->input('email'));
if ($response->isDeliverable()) {
// proceed
}
If your installed version exposes no facade, resolve the client from the container: app(\Juanparati\LaravelKickbox\Kickbox::class).
Validation rule, not controller branching. The highest-leverage integration is a custom rule so every form that accepts an email gets the same policy:
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Juanparati\LaravelKickbox\Facades\Kickbox;
class DeliverableEmail implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$result = Kickbox::verify($value);
if (! $result->isDeliverable()) {
$fail("The :attribute is not a deliverable address ({$result->reason()}).");
}
}
}
Decide your accept_all policy once. Corporate domains behind catch-all mail servers come back result: risky with accept_all: true. Blocking them will reject real customers; accepting them means you'll eat some bounces. Write the policy down in a config flag and reference it from the rule:
if ($result->isRisky() && $result->isAcceptAll() && config('kickbox.allow_accept_all')) {
return;
}
Cache aggressively. Kickbox bills per verification and results change slowly. Cache by normalized (lowercased, trimmed) address for 30–90 days:
$result = Cache::remember("kickbox:".sha1(Str::lower($email)), now()->addDays(60), fn () => Kickbox::verify($email));
Never verify on the hot path of a login or a password reset. Verification is an outbound HTTP call; do it on registration, on email change, and in a queued re-validation job — not in middleware.
Backfill job. For existing users, run a chunked queued job that verifies only addresses that have never bounced, writes a email_status column, and flags undeliverable rows for a re-engagement or suppression flow. Keep the raw payload in a json column so you can re-evaluate the policy later without paying for a second call.
Guard the key. Public keys are safe to ship in front-end JS for the balance endpoint; secret keys are not. If the config exposes both, keep them in separate env vars and never log the resolved config array.
^1.1 until you've confirmed.Http::fake() with a canned Kickbox payload, and add one test per result value including unknown.200 with result: unknown still means "we couldn't determine" — it is not success. Handle it explicitly (retry later or accept with a warning) rather than falling through to true.[email protected] distinctly. Normalize before both the API call and the cache key, or you'll pay twice and get inconsistent cache hits.disposable flag and refresh your cached results periodically.Cache::remember across the codebase. That decorator is also the seam you'll want when a future major version changes the response object.reason field, not the full response. Reasons like rejected_email, invalid_domain, low_deliverability are what support tickets need; the full payload can contain PII.How can I help you explore Laravel packages today?