Spaw PHP SDK: verify emails from PHP

updated · September 3, 2026

The official spaw/spaw-php package wraps every Spaw endpoint in a dependency-free client with one exception per API code. PHP 8.2 with curl and json.

Mechanism
Official client, no dependencies, injectable transport
Status
Built, awaiting publication on Packagist as spaw/spaw-php
Auth
Secret API key passed to the client
Cost
1 credit per answered lookup, undeliverable answers free
Requires
PHP 8.2 or newer with the curl and json extensions

The PHP client is a thin, dependency-free wrapper over the REST API described by the OpenAPI document: one method per endpoint, a Spaw\Response carrying the envelope's data and meta arrays exactly as the API sends them, and a typed exception for every error code. It needs PHP 8.2 with the curl and json extensions, and its HTTP transport is an interface you can replace to route requests through your own stack or fake the API in tests.

The package is built and will be published on Packagist as spaw/spaw-php. Until the listing is live, Laravel's Http::withToken() against the API reference does the same job in a few lines.

Install and verify one address

  1. Create a secret key on the API keys page and put it in your environment.
  2. Install the package once it is published:
composer require spaw/spaw-php
  1. Verify an address:
use Spaw\Client;

$spaw = new Client(getenv('SPAW_API_KEY'));

$result = $spaw->verifyEmail('[email protected]');

$result->data['deliverable'];   // "deliverable" | "risky" | "undeliverable"
$result->data['reason'];        // null, or e.g. "disposable", "mailbox_not_found"
$result->data['risk_score'];    // 0–100, an auditable sum of published weights
$result->creditsUsed();         // 1 for a deliverable or risky answer, 0 otherwise
$result->requestId();           // quote this when writing to support

In Laravel, bind the client as a singleton in a service provider and type-hint it where you need it:

$this->app->singleton(Client::class, fn () => new Client(config('services.spaw.key'), maxRetries: 2));

Everything the client does

Method Endpoint
verifyEmail($email, $callbackUrl = null, $callbackSecret = null) POST /email
verifyEmails($emails) POST /email/batch, up to 50
domain($domain) GET /email/domain/{domain}
createBulkJob($emails, $webhookUrl = null), getBulkJob($id), cancelBulkJob($id), downloadBulkResults($id, $variant = null) the bulk endpoints, up to 100,000 addresses; the results CSV comes back as a string
listSuppressions(page: 1), addSuppressions($emails), removeSuppression($id) the suppression list
reportFeedback($items), feedbackSummary() delivery outcomes and measured accuracy
account() the live credit balance
validatePhone($phone), lookupIp($ip) the phone and IP endpoints

A batch keeps the API's shape: data['results'] holds one {data, meta} per address in input order, and the top-level meta['stopped_reason'] is set instead of an exception when the balance runs out part-way, so paid results are never lost.

Handle errors

Every failure throws a Spaw\Exceptions\SpawException or a subclass carrying errorCode(), httpStatus(), requestId() and, for validation failures, errors() keyed by field. No credits are spent on a failed request.

use Spaw\Exceptions\InsufficientCreditsException;
use Spaw\Exceptions\RateLimitedException;
use Spaw\Exceptions\SpawException;

try {
    $result = $spaw->verifyEmail($email);
} catch (InsufficientCreditsException $e) {
    // nothing ran and nothing was billed
} catch (RateLimitedException $e) {
    sleep($e->retryAfter() ?? 1);
} catch (SpawException $e) {
    log($e->errorCode(), $e->getMessage(), $e->requestId());
}

AuthenticationException maps UNAUTHENTICATED, ValidationException maps VALIDATION_FAILED, NotFoundException covers a job or suppression entry that is not on the account, and Spaw\Exceptions\ConnectionException means no HTTP answer arrived at all. Pass maxRetries to the constructor to retry 429 and 5xx answers automatically, waiting for Retry-After when the server sends one.

What to do with the verdict

Branch on deliverable. Keep deliverable addresses, drop undeliverable ones, and route risky ones by reason: refuse disposable and likely_typo, decide role per use case, and treat catch_all as an unconfirmed rather than a bad address, using mailbox_confidence as the threshold. When did_you_mean is set, show it to the user.

Cost

One credit per fresh deliverable or risky verdict. Undeliverable verdicts, invalid input, repeats within seven days and the spaw.test test addresses cost nothing, and every account gets 10 free lookups a month. Use [email protected] and its siblings in your tests, or implement Spaw\Http\Transport to fake the API entirely.

Related

markdown version: /integrations/php-sdk.md

Verify addresses the same way

The Spaw API runs every check described here on each lookup, with an SMTP handshake that never sends mail. 10 free lookups a month, no card required.

Get your API key

More integrations