30% extra credits on top-ups within 24 hours of signup.

Contact leads with FullEnrich and Hail

Find a lead's work email with FullEnrich. Use your AI agent to prepare a message, send it through Hail, and track the result.

Agent workflow by Hail

Give your agent a useful sales task: take a relevant lead and start a conversation by email.

The workflow has three parts. Your prospect list identifies the person. FullEnrich finds their work email. Hail sends your agent's message.

This guide starts with one contact you have already selected. You can use a CRM, a research tool, or FullEnrich's separate Search API to build that list.

What you will build

Selected lead: name + company
             |
             v
FullEnrich: find the work email
             |
             v
Your agent: prepare a relevant message
             |
             v
Review the recipient and draft
             |
             v
Hail: send email and report the result

Start with one contact before connecting a larger list. Your agent can research the company and prepare a draft using verified facts. Keep the reason for contacting that person in your CRM.

You need a FullEnrich API key, a funded Hail workspace, a Hail API key, and a verified sending identity. Keep both keys on your server. FullEnrich enrichment and Hail email sending are separate services with separate usage charges.

Find the contact's work email

FullEnrich accepts a first name, last name, and company domain or name. It also accepts a LinkedIn profile URL. Its bulk enrichment endpoint supports a single contact in the data array.

Replace the example person and domain with your selected contact. Set FULLENRICH_API_KEY in your shell before running this request:

curl --fail-with-body --silent --show-error \
  https://app.fullenrich.com/api/v2/contact/enrich/bulk \
  -H "Authorization: Bearer $FULLENRICH_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{
    "name": "First lead email",
    "data": [{
      "first_name": "Maya",
      "last_name": "Chen",
      "domain": "example.com",
      "enrich_fields": ["contact.work_emails"],
      "custom": {"crm_contact_id": "lead-001"}
    }]
  }'

The response contains an enrichment_id. Save it as FULLENRICH_ENRICHMENT_ID. The first response starts the work; it does not contain a completed result.

Request the enrichment result after processing finishes:

curl --fail-with-body --silent --show-error \
  "https://app.fullenrich.com/api/v2/contact/enrich/bulk/$FULLENRICH_ENRICHMENT_ID" \
  -H "Authorization: Bearer $FULLENRICH_API_KEY" \
  --output enrichment-result.json

If the result is still processing, wait before checking again. FullEnrich recommends at least five to ten minutes between polling requests. In a deployed workflow, use its completion webhook and verify the signature before processing the result.

Wait for status: "FINISHED". Read the address and status from data[].contact_info.most_probable_work_email. Continue only when the address exists and its status is DELIVERABLE. Skip missing or uncertain addresses rather than asking the model to invent one.

Give your agent a specific writing task

An agent needs more than an email address. Give it your offer, a verified company fact, and a clear next step.

For example:

Prepare one short email for this contact. Use only the company facts in the supplied research. Connect our offer to one relevant task. End with one easy question. Return the subject and body for review.

Review the draft and selected recipient before your first send. The agent can use Hail's MCP connection or call the email API from your code.

An enriched address identifies a mailbox; it does not establish permission to contact the person. Hail marketing requests need a truthful recipient_consent value and a recorded consent_source. Your message also needs your business details and a working opt-out route.

Send the reviewed message with Hail

Create approved-message.json with your reviewed content and contact reference:

{
  "crm_contact_id": "lead-001",
  "campaign_id": "intro-v1",
  "subject": "A question about your appointment workflow",
  "body_text": "Hi Maya,\n\n[Your reviewed message, business details, and opt-out link.]",
  "recipient_consent": false,
  "consent_source": ""
}

Fill the message and consent fields from your actual records. Set HAIL_API_KEY and HAIL_FROM to your API key and verified sender address.

Save this as send-lead-email.mjs, then run node send-lead-email.mjs with Node.js 20 or later:

import { readFile } from 'node:fs/promises';
import { createHash } from 'node:crypto';

const result = JSON.parse(await readFile('enrichment-result.json', 'utf8'));
const draft = JSON.parse(await readFile('approved-message.json', 'utf8'));
const lead = result.data?.find(
  (item) => item.custom?.crm_contact_id === draft.crm_contact_id
);
const mailbox = lead?.contact_info?.most_probable_work_email;

if (result.status !== 'FINISHED' || mailbox?.status !== 'DELIVERABLE'
    || !mailbox.email) {
  throw new Error('Wait for a completed enrichment with a deliverable work email');
}
if (draft.recipient_consent !== true || !draft.consent_source?.trim()) {
  throw new Error('Record permission and its source before sending');
}
if (!draft.campaign_id || !draft.subject?.trim() || !draft.body_text?.trim()) {
  throw new Error('Complete the campaign reference and reviewed message');
}
if (!process.env.HAIL_API_KEY || !process.env.HAIL_FROM) {
  throw new Error('Set HAIL_API_KEY and HAIL_FROM');
}

const body = {
  from: process.env.HAIL_FROM,
  to: [mailbox.email],
  subject: draft.subject,
  body_text: draft.body_text,
  message_type: 'marketing',
  recipient_consent: draft.recipient_consent,
  consent_source: draft.consent_source,
  metadata: { crm_contact_id: draft.crm_contact_id, campaign_id: draft.campaign_id }
};
const fingerprint = createHash('sha256')
  .update(JSON.stringify(body)).digest('hex');

const response = await fetch('https://api.hail.so/v1/emails', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HAIL_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': `lead-email:${fingerprint}`
  },
  body: JSON.stringify(body),
  signal: AbortSignal.timeout(30000)
});
const email = await response.json();
if (!response.ok) throw new Error(`Hail rejected the send (${response.status})`);
console.log({ id: email.id, status: email.status });

The recipient comes from the enrichment result matched to your CRM contact. The reviewed draft supplies the subject and message. The idempotency key stays stable when you retry that same request.

Keep a durable send record keyed by the campaign and contact before running a larger list. API idempotency protects retries within its retention window; it is not a permanent campaign history.

Turn delivery and replies into the next action

Save the returned Hail email ID beside your CRM contact. A successful send means the provider accepted the message. It does not prove that someone read it.

Use Hail webhooks for delivery results and incoming email events. Your app can record a reply, stop on a bounce, or send a conversation to a person.

If you add follow-ups, your application or an external workflow tool must manage their timing. Hail's current send endpoint sends immediately. Stop follow-ups when the recipient replies or opts out.

FullEnrich supplies contact data. Your agent prepares the message. Hail handles email transport and events. That division gives you a useful workflow to build from.

For the smallest first test, follow send an email with an API. Then connect the same send to your lead list.