Plain PHP with cURL
Set both a connect timeout and a total timeout, or a slow network can hold a PHP worker for minutes. The function returns null when the check could not be completed, so the caller decides how to fail. For a signup form, fail open.
<?php
function veridock_verify(string $email): ?array
{
$url = 'https://app.veridock.io/v1/verify?' . http_build_query(['email' => $email, 'timeout' => 4000]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('VERIDOCK_API_KEY')],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_TIMEOUT => 8,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$error = curl_error($ch);
if ($body === false) {
error_log("VeriDock unreachable: $error");
return null;
}
$data = json_decode($body, true);
if ($status !== 200) {
error_log("VeriDock error $status: " . ($data['error']['code'] ?? 'unknown'));
return null;
}
return $data;
}Use it in a form handler
Reject addresses that will bounce and throwaway inboxes. Accept everything else, including unknown results.
<?php
require __DIR__ . '/veridock.php';
$email = trim($_POST['email'] ?? '');
$result = veridock_verify($email);
if ($result && $result['status'] === 'undeliverable') {
$error = $result['suggestion']
? "Did you mean {$result['suggestion']}?"
: 'That address cannot receive email.';
} elseif ($result && $result['status'] === 'risky' && $result['reason'] === 'disposable') {
$error = 'Please use a permanent email address.';
}
if (isset($error)) {
http_response_code(422);
echo htmlspecialchars($error);
exit;
}
create_user($email); // your existing signup logicA Laravel validation rule
In Laravel, wrap the same policy in a validation rule. The HTTP client's retry() handles connection errors, 429 and 5xx responses with a short pause. With throw: false a failed response is returned instead of thrown, and the catch covers the network being down, so a failed check lets the address through instead of showing your user an error.
Add the key to config/services.php as 'veridock' => ['key' => env('VERIDOCK_API_KEY')].
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
class DeliverableEmail implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
$response = Http::withToken(config('services.veridock.key'))
->timeout(8)
->retry(2, 500, fn ($e) => ! $e instanceof RequestException || $e->response->status() === 429 || $e->response->serverError(), throw: false)
->get('https://app.veridock.io/v1/verify', ['email' => $value, 'timeout' => 4000]);
} catch (ConnectionException) {
return; // fail open: never block a signup because the check itself failed
}
if (! $response->successful()) {
return;
}
$result = $response->json();
if ($result['status'] === 'undeliverable') {
$fail($result['suggestion']
? "The :attribute looks mistyped. Did you mean {$result['suggestion']}?"
: 'The :attribute cannot receive email.');
} elseif ($result['status'] === 'risky' && $result['reason'] === 'disposable') {
$fail('Please use a permanent :attribute.');
}
}
}Apply the rule
Put it after Laravel's own email rule, so malformed input is rejected locally without an API call.
public function rules(): array
{
return [
'email' => ['required', 'email', 'max:254', new DeliverableEmail],
];
}Reference
Frequently asked questions
Keep reading
Other guides: Next.js · Node.js · Python · React · Supabase · Zapier, Make and n8n