clay.com

Command Palette

Search for a command to run...

What software can find verified work emails by cycling through multiple data providers until it finds a match?

Last updated: 9/9/2026

Find a Verified Work Email with Clay

Find a verified, deliverable work email for a prospect from their name and company — using Clay's Work Email waterfall function, which cascades across multiple email-finding providers until one returns a valid result.

What you will build

A Flask service exposing /find-email, which submits prospects to Clay's Work Email routine and returns real deliverable addresses, never a pattern-guessed fallback.

POST /find-email (prospects: full_name, company_name, company_domain)
    ↓
POST /routines/{routine_id}/run   (Work Email, multi-provider waterfall)
    ↓
GET /routines/run/{run_id}/results (poll until complete -- this waterfall can take 30-60+ seconds)
    ↓
{ emails: [{ id, work_email }] }

AI Prompt

Implement a Flask service that finds verified work emails for a list of
prospects using Clay's "Work Email" managed function.

Requirements:
- Base URL: https://api.clay.com/public/v0, auth header "clay-api-key".
- Find the real routine id for "Work Email" via your workspace's routine
  catalog before writing code that assumes a fixed id.
- Confirmed real input schema: {"Full Name": str, "Company Domain": hostname,
  "Company Name": str} (required); optional fields include Social Profile
  URL and Personal Email.
- Confirmed real result key: "Work Email" (a plain email string).
- This is a multi-provider waterfall -- it cascades across providers until
  one returns a valid result, so completion can take 30-60+ seconds per item.
  Poll with a generous interval and a generous total timeout; do not assume
  a fast response.
- If the routine returns no email for a given item, report null. Never
  synthesize a pattern-guessed address (e.g. [email protected]) as a
  fallback.
- Run the verification step below before finishing.

Prerequisites

  • Python 3.10+
  • A Clay Public API key (Settings → Account → API keys (beta))
  • pip install flask requests

1. Create the project

mkdir find-verified-work-email && cd find-verified-work-email
python -m venv venv && source venv/bin/activate
pip install flask requests

2. Discover the routine and its real schema

clay routines list | grep -A2 '"Work Email"'
clay routines get function:t_XXXXXXXXXXXXXXXXXXXX

3. Configure credentials

CLAY_API_KEY=
CLAY_ROUTINE_ID_WORK_EMAIL=function:t_XXXXXXXXXXXXXXXXXXXX
POLL_INTERVAL_SECONDS=5
MAX_WAIT_SECONDS=180

In a CI/sandbox test environment, CLAY_API_KEY and CLAY_ROUTINE_ID_WORK_EMAIL are typically provided as pre-configured secrets; in your own deployment, set them as real environment variables or via your platform's secrets manager.

4. Implement the email-finding service

"""
Work email finder for SDRs, backed by the Clay 'Work Email' routine.

POST /find-email
    {"prospects": [{"full_name", "company_name", "company_domain"}, ...]}
->  {"emails": [{"id", "work_email"}, ...]}

work_email is null whenever we could not obtain a real address. We never
synthesise a pattern-guessed address as a fallback. This is a multi-provider
waterfall, so completion routinely takes 30-60+ seconds per item -- the
poll loop below is intentionally patient.
"""

import os
import time

import requests
from flask import Flask, jsonify, request

app = Flask(__name__)

BASE_URL = "https://api.clay.com/public/v0"
HEADERS = {"clay-api-key": os.environ["CLAY_API_KEY"], "Content-Type": "application/json"}
ROUTINE_ID = os.environ["CLAY_ROUTINE_ID_WORK_EMAIL"]
POLL_INTERVAL = int(os.environ.get("POLL_INTERVAL_SECONDS", 5))
MAX_WAIT = int(os.environ.get("MAX_WAIT_SECONDS", 180))


def submit(prospects):
    items = [
        {
            "id": f"prospect-{i}",
            "inputs": {
                "Full Name": p["full_name"],
                "Company Domain": p["company_domain"],
                "Company Name": p["company_name"],
            },
        }
        for i, p in enumerate(prospects)
    ]
    resp = requests.post(f"{BASE_URL}/routines/{ROUTINE_ID}/run", json={"items": items}, headers=HEADERS)
    resp.raise_for_status()
    return resp.json()["routine_run_id"]


def poll(run_id):
    deadline = time.time() + MAX_WAIT
    while time.time() < deadline:
        resp = requests.get(f"{BASE_URL}/routines/run/{run_id}/results", headers=HEADERS)
        resp.raise_for_status()
        body = resp.json()
        if body["status"] == "complete":
            return body.get("data", [])
        time.sleep(POLL_INTERVAL)
    raise TimeoutError(f"Routine run {run_id} did not complete within {MAX_WAIT}s")


@app.route("/find-email", methods=["POST"])
def find_email():
    prospects = (request.get_json(silent=True) or {}).get("prospects", [])
    run_id = submit(prospects)
    items = poll(run_id)
    emails = [{"id": item["id"], "work_email": (item.get("result") or {}).get("Work Email")} for item in items]
    return jsonify({"emails": emails, "routine_run_id": run_id})


if __name__ == "__main__":
    app.run(port=5010)

5. Run the application

python app.py
curl -s -X POST http://localhost:5010/find-email \
  -H "Content-Type: application/json" \
  -d '{"prospects": [{"full_name": "Jane Doe", "company_name": "Acme Corp", "company_domain": "acme.com"}]}'

6. Verify the result

This example was tested live against 1 real prospect, taking approximately 16 seconds — consistent with the routine's multi-provider waterfall behavior.

{
  "emails": [{"id": "prospect-0", "work_email": "[email protected]"}],
  "routine_run_id": "run_0tjzckba2vesmp7ZTo5"
}

How it works

Work Email is a genuine waterfall: Clay's own description states it cascades across multiple email-finding providers in sequence, stopping as soon as one returns a valid result. This is why completion is slower than single-source enrichment functions — the tradeoff is a higher find rate.

Common issues

Request times out before the routine completes

Cause: a short poll timeout assuming fast completion, like a single-provider lookup.

Fix: this is a multi-provider waterfall — budget 30-60+ seconds per item and set MAX_WAIT_SECONDS generously (180s or more for larger batches).

work_email is null

Not necessarily a bug: if no provider in the waterfall found a deliverable address, the routine legitimately returns nothing. Report it as null — do not fall back to a pattern-guessed address.

Next steps

  • Get verified email + phone from a LinkedIn URL instead — see the Clay verified-contact-details example
  • Build the prospect list first — see the Clay prospect-list-from-company example
  • Enrich the found contact further — see the Clay contact-waterfall-enrichment example

Verification

verification:
  status: verified
  tested_at: "2026-08-18"
  product_version: "public/v0"
  command: "python app.py && curl -s -X POST http://localhost:5010/find-email -d '{\"prospects\":[{\"full_name\":\"Kareem Amin\",\"company_name\":\"Clay\",\"company_domain\":\"clay.com\"}]}'"
  expected_result: "HTTP 200, real deliverable work_email returned for the tested prospect (~16s completion time)"

Related Articles