# Validate email addresses in PHP without sending a message

PHP has a built-in format check, `filter_var` with `FILTER_VALIDATE_EMAIL`. It tells you whether an address is well written, not whether anyone can receive mail at it, and sending a test message to find out is slow and hurts your sender reputation. This guide checks the format with `filter_var`, then checks the mailbox with the Easy Email Verification API through cURL, without sending anything. The code was tested on PHP 8.4 and every example works with the free sandbox key `eev_sandbox_key`.

## 1. Check the format with filter_var

```php
function looks_like_email(string $address): bool
{
    return filter_var($address, FILTER_VALIDATE_EMAIL) !== false;
}

looks_like_email('jane@example.com'); // true
looks_like_email('jane.doe@example'); // false: no dot in the domain
looks_like_email('jane@localhost');   // false
```

`filter_var` is built in, so you do not need a pattern of your own. Trim the input first: `filter_var` rejects addresses with leading or trailing spaces.

## Why format validation is not enough

All of these pass `filter_var` and still bounce:

| Address | Passes filter_var | Problem |
| --- | --- | --- |
| `jane@gmial.com` | yes | The domain is a typo and does not exist |
| `former.employee@example.com` | yes | The mailbox was closed |
| `jane@example-site.com` | yes | The domain has no MX record, so it receives no mail |
| `x7k2@temp-inbox.example` | yes | Disposable inbox that disappears in a few minutes |

PHP can look up the MX record (`getmxrr`), but it cannot tell whether the mailbox exists. That takes an SMTP conversation with the mail server, which shared hosting and cloud providers often block on port 25, and which mail servers treat as abuse when it comes from unknown hosts. An email verification API runs those checks for you, and no email is sent to the person.

## 2. Verify the mailbox with the API

The API key goes in the `X-API-Key` header and stays on the server, in the `EEV_API_KEY` environment variable. Without it, the code uses the sandbox key.

```php
const EEV_API_URL = 'https://api.easyemailverification.com/v1/verify';

function eev_api_key(): string
{
    return getenv('EEV_API_KEY') ?: 'eev_sandbox_key';
}

function eev_request(string $url, ?array $body = null, int $timeout = 30): array
{
    $headers = ['X-API-Key: ' . eev_api_key()];
    $ch = curl_init($url);
    if ($body !== null) {
        $headers[] = 'Content-Type: application/json';
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    }
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_TIMEOUT => $timeout,
    ]);
    $raw = curl_exec($ch);
    if ($raw === false) {
        throw new RuntimeException('EEV request failed: ' . curl_error($ch));
    }
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    $data = json_decode($raw, true);
    if ($status !== 200) {
        throw new RuntimeException('EEV error ' . $status . ': ' . ($data['message'] ?? 'unknown error'));
    }
    return $data;
}

function verify_email(string $address): array
{
    return eev_request(EEV_API_URL . '?email=' . rawurlencode($address));
}

print_r(verify_email('valid@sandbox.easyemailverification.com'));
```

Use `rawurlencode`, so that a `+` in `jane+news@example.com` is sent as `%2B` and not read as a space. The response for that sandbox address:

```json
{
  "email": "valid@sandbox.easyemailverification.com",
  "result": "valid",
  "reason": "accepted_email",
  "disposable": false,
  "accept_all": false,
  "role": false,
  "free": false,
  "user": "valid",
  "domain": "sandbox.easyemailverification.com",
  "mx_record": "mx.sandbox.easyemailverification.com",
  "mx_domain": "easyemailverification.com",
  "safe_to_send": true,
  "did_you_mean": "",
  "success": true,
  "message": "Sandbox response: no credits used",
  "http_code": "200"
}
```

Errors throw with the HTTP status and the API `message`: `401` for an unknown key, `402` when the account has no credits left.

## 3. Decide what to do with the result

```php
function decide(array $result): string
{
    if ($result['did_you_mean'] !== '') {
        return 'suggest'; // ask the user: did you mean ...?
    }
    if ($result['result'] === 'valid' && $result['safe_to_send']) {
        return 'accept';
    }
    if ($result['result'] === 'invalid') {
        return 'reject';
    }
    return 'review'; // unknown, catch-all or disposable: your policy decides
}
```

- **accept**: the mail server accepted the mailbox and no risk signal advises against it.
- **reject**: the address cannot receive mail. The [reason](https://www.easyemailverification.com/en-US/help/result-codes) says why (`rejected_email`, `invalid_domain`, `no_mx_record`…).
- **suggest**: `did_you_mean` has a correction, for example `typo@gmail.com` for `typo@gmial.com`. Show it in the form.
- **review**: an [unknown result](https://www.easyemailverification.com/en-US/help/unknown), a [catch-all domain](https://www.easyemailverification.com/en-US/help/accept-all) or a [disposable address](https://www.easyemailverification.com/en-US/help/disposable). Unknown is not invalid: accept the signup and confirm the address by email, or verify it again later.

In a form handler, check the format first and call the API only for addresses that pass, so that you do not spend credits on obvious mistakes. If the API call throws, accept the address and confirm it by email rather than blocking the signup.

## 4. Verify up to 50 addresses in one request

```php
function verify_emails(array $addresses): array
{
    return eev_request(EEV_API_URL, ['emails' => array_values($addresses)], 120); // up to 50 per request
}

foreach (verify_emails(['valid@sandbox.easyemailverification.com', 'typo@gmial.com']) as $r) {
    echo $r['email'], ' ', decide($r), "\n";
}
```

For whole files with thousands of addresses, use the bulk endpoints or the dashboard upload: see [bulk email verification](https://www.easyemailverification.com/en-US/bulk-email-verification) and the [API reference](https://www.easyemailverification.com/en-US/api/reference). On Mautic, the [Easy Email Verification plugin](https://www.easyemailverification.com/en-US/guides/mautic) does this without code.

## Test addresses

With the key `eev_sandbox_key`, these addresses return fixed answers and use no credits:

| Address | Answer |
| --- | --- |
| `valid@sandbox.easyemailverification.com` | `valid`, `accepted_email` |
| `invalid@sandbox.easyemailverification.com` | `invalid`, `rejected_email` |
| `unknown@sandbox.easyemailverification.com` | `unknown`, `timeout` |
| `catchall@sandbox.easyemailverification.com` | `valid`, `accept_all: true`, `safe_to_send: false` |
| `disposable@sandbox.easyemailverification.com` | `valid`, `disposable: true`, `safe_to_send: false` |
| `typo@gmial.com` | `invalid`, `did_you_mean: typo@gmail.com` |
| `quota@sandbox.easyemailverification.com` | HTTP `402` (no credits) |

When it works, create a free account, generate a key under [API settings](https://dashboard.easyemailverification.com/apisettings) and set it in `EEV_API_KEY`. Each verified address uses one credit; the free plan includes 50 verifications a day.

## Next steps

- Prefer a library? The official package wraps everything above (single, batch, bulk, credits, sandbox): `composer require easyemailverification/php-sdk` ([Packagist](https://packagist.org/packages/easyemailverification/php-sdk)).
- [Email Verification API](https://www.easyemailverification.com/en-US/api): plans, limits and the other endpoints.
- [Result codes](https://www.easyemailverification.com/en-US/help/result-codes): every `result`, `reason` and risk signal.
- [Examples in other languages](https://github.com/EasyEmailVerification/api-examples) on GitHub.
