Email Validation API in PHP: Complete Guide

Use AI to summarize this article and ask questions

Grant Ammons
Grant Ammons – Founder April 18, 2026

Email Validation API in PHP: Complete Guide

Learn how to validate email addresses in PHP using the Truelist API. Step-by-step tutorial with code examples for cURL, Guzzle, and batch validation.

TL;DR: Use the Truelist API to validate emails in PHP with a POST request to https://api.truelist.io/api/v1/verify. Works with native cURL and Guzzle. Pass your API key in the Authorization header and an email in the JSON body — you get back deliverability status, risk flags, and detailed reason codes.

Email validation in PHP gives you a reliable way to verify whether an email address actually exists and can receive mail — something regex and filter_var() simply cannot do. Whether you’re building a Laravel application, a WordPress plugin, a custom API, or a legacy PHP system, integrating real-time email validation protects your sender reputation and keeps your email lists clean.

This guide covers integrating the Truelist email validation API into PHP applications using both cURL (available in every PHP installation) and Guzzle (the modern HTTP client standard), with practical examples for single and batch validation.

Why Validate Emails in PHP?

PHP powers a massive share of the web — WordPress alone runs over 40% of all websites. If your PHP application collects email addresses through contact forms, registration pages, checkout flows, or newsletter signups, you need validation that goes beyond basic format checking.

Here’s what filter_var($email, FILTER_VALIDATE_EMAIL) misses:

  • Non-existent mailboxesperfectly.formatted@gmail.com passes format checks but might not be a real account
  • Dead domains — the domain might have expired or no longer accept mail
  • Disposable emails — throwaway addresses from services like Mailinator or Guerrilla Mail
  • Spam traps — addresses specifically designed to catch senders with poor list hygiene
  • Catch-all domains — servers that accept mail for any address, making verification uncertain

Every invalid email that reaches your sending infrastructure increases your bounce rate, damages your sender reputation with ISPs, and wastes resources. An API-based validation checks DNS records, performs SMTP verification, and cross-references known disposable providers — all in real time.

Getting Started with the Truelist API

Before writing any PHP code, get your API credentials:

  1. Sign up for a free account at truelist.io
  2. Navigate to your dashboard and find the API section
  3. Generate an API key
  4. Store it securely — use environment variables or a .env file, never hardcode it

The verification endpoint is:

POST https://api.truelist.io/api/v1/verify

For complete endpoint documentation, rate limits, and response schemas, see the API documentation.

Single Email Validation with cURL

PHP’s cURL extension is available in virtually every PHP installation, making it the most portable option. Here’s a clean validation function:

<?php

function validateEmail(string $email): array
{
    $apiKey = getenv('TRUELIST_API_KEY');
    $url = 'https://api.truelist.io/api/v1/verify';

    $ch = curl_init($url);

    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $apiKey,
        ],
        CURLOPT_POSTFIELDS => json_encode(['email' => $email]),
        CURLOPT_TIMEOUT => 10,
        CURLOPT_CONNECTTIMEOUT => 5,
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $error = curl_error($ch);

    curl_close($ch);

    if ($error) {
        throw new RuntimeException("cURL error: {$error}");
    }

    if ($httpCode !== 200) {
        throw new RuntimeException(
            "Truelist API error: HTTP {$httpCode}{$response}"
        );
    }

    return json_decode($response, true);
}

// Usage
try {
    $result = validateEmail('test@example.com');
    echo "Status: {$result['status']}\n";

    if ($result['status'] === 'valid') {
        echo "Email is deliverable\n";
    } elseif ($result['status'] === 'invalid') {
        echo "Email is not deliverable — do not send\n";
    } else {
        echo "Status is uncertain — proceed with caution\n";
    }
} catch (RuntimeException $e) {
    echo "Validation failed: {$e->getMessage()}\n";
}

Set your API key as an environment variable:

export TRUELIST_API_KEY=your_api_key_here
php validate.php

The CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT settings prevent your script from hanging if the API is slow or unreachable.

Single Email Validation with Guzzle

For modern PHP applications, Guzzle provides a cleaner interface with built-in PSR-7 support, async capabilities, and middleware. Install it via Composer:

composer require guzzlehttp/guzzle

Here’s the same validation using Guzzle:

<?php

require_once 'vendor/autoload.php';

use GuzzleHttpClient;
use GuzzleHttpExceptionGuzzleException;

function validateEmail(string $email): array
{
    $apiKey = getenv('TRUELIST_API_KEY');

    $client = new Client([
        'base_uri' => 'https://api.truelist.io',
        'timeout' => 10,
        'connect_timeout' => 5,
    ]);

    try {
        $response = $client->post('/api/v1/verify', [
            'headers' => [
                'Authorization' => 'Bearer ' . $apiKey,
            ],
            'json' => [
                'email' => $email,
            ],
        ]);

        return json_decode(
            $response->getBody()->getContents(),
            true
        );
    } catch (GuzzleException $e) {
        throw new RuntimeException(
            "Validation failed: {$e->getMessage()}"
        );
    }
}

// Usage
try {
    $result = validateEmail('test@example.com');
    echo "Status: {$result['status']}\n";
} catch (RuntimeException $e) {
    echo "Error: {$e->getMessage()}\n";
}

Guzzle automatically handles JSON encoding, content-type headers, and HTTP error detection. It also throws descriptive exceptions for network failures, timeouts, and non-2xx responses.

Batch Validation

When you need to validate a large email list — a CSV import, a database cleanup, or a migration — processing them in batches with Guzzle’s async capabilities is the way to go:

<?php

require_once 'vendor/autoload.php';

use GuzzleHttpClient;
use GuzzleHttpPool;
use GuzzleHttpPsr7Request;
use GuzzleHttpExceptionRequestException;

function validateBatch(array $emails, int $concurrency = 5): array
{
    $apiKey = getenv('TRUELIST_API_KEY');
    $client = new Client([
        'base_uri' => 'https://api.truelist.io',
        'timeout' => 10,
    ]);

    $results = [];

    $requests = function () use ($emails, $apiKey) {
        foreach ($emails as $email) {
            yield $email => new Request(
                'POST',
                '/api/v1/verify',
                [
                    'Content-Type' => 'application/json',
                    'Authorization' => 'Bearer ' . $apiKey,
                ],
                json_encode(['email' => $email])
            );
        }
    };

    $pool = new Pool($client, $requests(), [
        'concurrency' => $concurrency,
        'fulfilled' => function ($response, $email) use (&$results) {
            $data = json_decode(
                $response->getBody()->getContents(),
                true
            );
            $results[] = array_merge(['email' => $email], $data);
        },
        'rejected' => function (RequestException $e, $email) use (&$results) {
            $results[] = [
                'email' => $email,
                'status' => 'error',
                'error' => $e->getMessage(),
            ];
        },
    ]);

    $pool->promise()->wait();

    return $results;
}

// Usage
$emails = [
    'valid@gmail.com',
    'invalid@nonexistentdomain.xyz',
    'disposable@tempmail.com',
    'typo@gmial.com',
    'real-user@company.com',
];

$results = validateBatch($emails, 3);

$valid = array_filter($results, fn($r) => $r['status'] === 'valid');
$invalid = array_filter($results, fn($r) => $r['status'] === 'invalid');
$errors = array_filter($results, fn($r) => $r['status'] === 'error');

echo "Valid: " . count($valid) . "\n";
echo "Invalid: " . count($invalid) . "\n";
echo "Errors: " . count($errors) . "\n";

Guzzle’s Pool class manages concurrent requests with a configurable concurrency limit. Setting it to 3-5 keeps you well within API rate limits while still processing lists much faster than sequential requests.

For very large lists (100k+ emails), consider using bulk email verification through the Truelist dashboard, which handles queuing, retries, and results delivery automatically.

Handling API Responses

The Truelist API returns detailed JSON responses. Here’s a practical function to categorize emails and decide what to do with them:

<?php

function categorizeEmail(array $result): array
{
    $status = $result['status'] ?? 'unknown';
    $isDisposable = $result['is_disposable'] ?? false;
    $isRoleBased = $result['is_role_based'] ?? false;

    // Hard reject — mailbox does not exist
    if ($status === 'invalid') {
        return [
            'action' => 'reject',
            'reason' => $result['reason'] ?? 'Invalid email address',
        ];
    }

    // Disposable/throwaway email
    if ($isDisposable) {
        return [
            'action' => 'reject',
            'reason' => 'Disposable email address not allowed',
        ];
    }

    // Role-based (info@, support@, admin@)
    if ($isRoleBased) {
        return [
            'action' => 'flag',
            'reason' => 'Role-based address — may have lower engagement',
        ];
    }

    // Valid and deliverable
    if ($status === 'valid') {
        return [
            'action' => 'accept',
            'reason' => 'Email is valid and deliverable',
        ];
    }

    // Catch-all or unknown
    return [
        'action' => 'accept_with_caution',
        'reason' => "Status: {$status} — monitor for bounces",
    ];
}

Key fields in the API response:

  • statusvalid, invalid, unknown, or catch-all
  • reason — human-readable explanation for invalid or risky addresses
  • is_disposable — whether the address uses a throwaway email service
  • is_role_based — whether it’s a generic address like info@ or admin@

Error Handling

Production PHP applications need resilient API integration. Here’s a wrapper with retry logic, rate limit handling, and proper exception management:

<?php

function validateWithRetry(
    string $email,
    int $maxRetries = 3
): array {
    $apiKey = getenv('TRUELIST_API_KEY');
    $url = 'https://api.truelist.io/api/v1/verify';

    for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
        $ch = curl_init($url);

        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'Authorization: Bearer ' . $apiKey,
            ],
            CURLOPT_POSTFIELDS => json_encode(['email' => $email]),
            CURLOPT_TIMEOUT => 10,
            CURLOPT_CONNECTTIMEOUT => 5,
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);

        curl_close($ch);

        // cURL error — retry with backoff
        if ($error) {
            if ($attempt === $maxRetries) {
                return ['status' => 'unknown', 'error' => $error];
            }
            sleep(pow(2, $attempt));
            continue;
        }

        // Rate limited — respect Retry-After header
        if ($httpCode === 429) {
            $retryAfter = 2;
            if (preg_match('/Retry-After:s*(d+)/i', $response, $m)) {
                $retryAfter = (int) $m[1];
            }
            sleep($retryAfter);
            continue;
        }

        // Server error — retry with backoff
        if ($httpCode >= 500) {
            if ($attempt === $maxRetries) {
                return [
                    'status' => 'unknown',
                    'error' => "Server error: HTTP {$httpCode}",
                ];
            }
            sleep(pow(2, $attempt));
            continue;
        }

        // Client error (not rate limit) — don't retry
        if ($httpCode >= 400) {
            return [
                'status' => 'error',
                'error' => "HTTP {$httpCode}: {$response}",
            ];
        }

        // Success
        return json_decode($response, true);
    }

    return ['status' => 'unknown', 'error' => 'Max retries exceeded'];
}

This handles the critical failure scenarios:

  1. cURL errors (DNS failures, connection refused) — retries with exponential backoff
  2. Rate limiting (429) — respects the Retry-After header before retrying
  3. Server errors (5xx) — retries since these are typically transient
  4. Client errors (4xx) — returns immediately since retrying won’t help

Best Practices for Email Validation in PHP

Validate at the point of entry. Don’t wait for a cron job to catch bad emails. Validate in real time when users submit forms — it’s a better user experience and prevents bad data from entering your database. Use AJAX validation for immediate feedback without a full page reload.

Never hardcode your API key. Use getenv() to read from environment variables, or use a .env file with a library like vlucas/phpdotenv. Keep your .env out of version control — add it to .gitignore.

Reuse HTTP clients. In Guzzle, create one Client instance and reuse it across requests. Each new client instance creates a new connection pool. In long-running PHP processes (workers, daemons), this makes a significant difference.

Set timeouts on every request. PHP’s default cURL timeout is 0 (no timeout), which means a stalled API call can hang your entire process. Always set both CURLOPT_TIMEOUT (total time) and CURLOPT_CONNECTTIMEOUT (connection time).

Fail open in user-facing code. If the Truelist API is temporarily unavailable, don’t block form submissions. Accept the email and queue it for validation later. A brief API outage shouldn’t cost you customers.

Cache results to reduce API calls. Use Redis, Memcached, or even a database table to cache validation results for 24-48 hours. The same email won’t change status frequently, and caching reduces your API usage significantly for login forms and repeat submissions.

Run list hygiene on a schedule. Email addresses decay — people change jobs, domains expire, and mailboxes get abandoned. One-time validation at signup isn’t enough. Schedule regular re-validation of your full list to catch addresses that have gone stale.

Use filter_var() as a pre-check. Before making an API call, run filter_var($email, FILTER_VALIDATE_EMAIL) to catch obvious formatting errors locally. This saves API calls for clearly invalid input like empty strings or addresses missing the @ sign.


Stop validating once and hoping for the best. Truelist’s recurring validation automatically re-checks your lists on a schedule — catching new bounces, dead mailboxes, and risky addresses before they damage your sender reputation. No credits, no per-email charges.

Set up recurring validation →

Ready to put Truelist
to the test?

Find out if Truelist is right for you in under 10 minutes.

Free plan available. No credit card required.