---
title: Verify an email address
description: Check whether an email address is deliverable before you send to it, read the status looot normalizes across providers, and know what a catch-all or risky result means.
---

`people.email.verify` checks one address at a time. Use it on addresses you already have, or after
`people.email.find` on an address you just found (see [Enrich a lead list](/guides/enrich-leads)).

## What it costs

Read the live price with [`search`](/mcp-tools/search) before you run a batch:

```json MCP
search {"filters": {"capability": "people.email.verify"}, "prefer": "cheapest"}
```

On 2026-09-28 the cheapest listed `people.email.verify` provider priced around $0.0019 per call.
Multiply by the number of addresses and check [`balance`](/mcp-tools/balance) first for a batch.

1. **Run the check**

    Send `email`. Add [`fallback`](/concepts/fallback) so a provider that cannot reach the mailbox does not end the check.

    <CodeGroup>

    ```json MCP
    run {"endpointId": "job:people.email.verify", "input": {"email": "jane.doe@example.com"}, "idempotencyKey": "verify-jane-doe-1", "fallback": true}
    ```

    ```bash REST
    curl -s "https://api.looot.ai/v1/runs?wait=20" \
      -H "Authorization: Bearer $LOOOT_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "endpointId": "job:people.email.verify",
        "input": {"email": "jane.doe@example.com"},
        "idempotencyKey": "verify-jane-doe-1",
        "fallback": true
      }'
    ```

    ```bash CLI
    looot run job:people.email.verify \
      --input '{"email": "jane.doe@example.com"}' \
      --wait
    ```

    </CodeGroup>

    :::note
    looot 1.1.0 does not accept `--fallback` on `looot run`. The CLI call above tries one provider only.
    :::

2. **Read the status**

    `normalized.fields.status` is one of five values:

    | Status | What it means |
    | --- | --- |
    | `valid` | the mailbox exists and accepts mail |
    | `invalid` | the mailbox does not exist or the domain rejects it |
    | `catch_all` | the domain accepts any address at it, so this one is unproven either way |
    | `risky` | the provider found signals against sending (a role address, a disposable domain, a full mailbox) |
    | `unknown` | the provider could not reach a verdict, or answered with something looot does not recognize |

    `normalized.fields.deliverable` is a plain true/false, present only when the provider itself
    reports deliverability as a separate field.

3. **Decide what to do with each status**

    - `valid`: safe to send to.
    - `invalid`: drop it.
    - `catch_all` and `risky`: do not send automatically. Show these to a person, or run a second
      provider with `fallback` and compare.
    - `unknown`: treat like `catch_all`, and note which provider answered ([`servedProviderId`](/concepts/served-provider)) in case
      a different one does better on that domain.

## What the answer looks like

| Field | What it is |
| --- | --- |
| `status` | run status: `completed`, `failed`, `queued`, `running` |
| [`outcome`](/concepts/outcomes) | `hit`, `weak`, `miss`, `error`, `rejected`, `skipped` or `pending` |
| `servedProviderId` | the provider that actually answered |
| `actualCost` | what this run charged |
| `normalized.fields` | `email`, `status`, `deliverable` |
| `route.summary` | fallback runs only, e.g. "zerobounce: guessed ($0.01). hunter: found ($0.0245). Charged $0.0345." |

:::tip
`outcome` and `normalized.fields.status` can disagree on purpose. A `catch_all` verdict counts as
an `outcome` of `weak`, since it neither confirms nor rejects the address; treat `normalized.fields.status`
as the answer to show a person.
:::

## On a miss or error

- The provider cannot reach a verdict: `status` is `unknown`, `outcome` is often `miss`. With
  `fallback`, the next provider gets a try inside the same hold.
- A malformed address never reaches a provider: the run fails with [`validation_error`](/errors/rest-errors#validation_error) and charges
  nothing. The error names the field, e.g. "email: not-an-email is not an email address".
- [`insufficient_balance`](/errors/rest-errors#insufficient_balance): the run is blocked and not charged. Use [`top_up`](/mcp-tools/top-up), then retry with a new
  [`idempotencyKey`](/concepts/idempotency).

<Related />
