> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anyformat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Email lead extraction

> Turn an inbound sales email into a structured lead: sender, company, inquiry type, urgency and topics

**Nodes used:** [Parse](/guides/nodes/parse), [Extract](/guides/nodes/extract)

This workflow reads an email body and pulls out who wrote it, what they want and how urgent it is. The API takes files only, so the email body goes up as a `.txt` file; the Python SDK does that for you when you pass `text=`.

## The graph

<CodeGroup>
  ```json Graph theme={null}
  {
    "name": "Email lead extractor",
    "nodes": [
      { "id": "parse_1", "type": "parse" },
      {
        "id": "extract_1",
        "type": "extract",
        "extraction_schema": {
          "fields": [
            { "name": "sender_name",  "description": "Full name of the person who sent the email", "data_type": "string" },
            { "name": "sender_email", "description": "Email address of the sender", "data_type": "string" },
            { "name": "company_name", "description": "Company or organization the sender represents", "data_type": "string" },
            {
              "name": "inquiry_type",
              "description": "The primary category of this inquiry",
              "data_type": "enum",
              "enum_options": [
                { "name": "pricing",      "description": "Pricing or cost inquiry" },
                { "name": "demo_request", "description": "Request for a product demo" },
                { "name": "support",      "description": "Technical support or help request" },
                { "name": "partnership",  "description": "Partnership or integration inquiry" },
                { "name": "other",        "description": "Other inquiry type" }
              ]
            },
            {
              "name": "urgency",
              "description": "How urgent this request appears based on language and deadlines mentioned",
              "data_type": "enum",
              "enum_options": [
                { "name": "low",    "description": "No urgency signals" },
                { "name": "medium", "description": "Moderate urgency or soft deadline" },
                { "name": "high",   "description": "Explicit deadline or urgent language" }
              ]
            },
            {
              "name": "topics",
              "description": "Products or areas of interest mentioned in the email",
              "data_type": "multi_select",
              "enum_options": [
                { "name": "enterprise",  "description": "Enterprise plan or features" },
                { "name": "integration", "description": "Integration or API capabilities" },
                { "name": "pricing",     "description": "Pricing or billing" },
                { "name": "security",    "description": "Security or compliance" }
              ]
            },
            { "name": "summary", "description": "A one-sentence summary of what the sender is asking for", "data_type": "string" }
          ]
        }
      }
    ],
    "edges": [{ "source": "parse_1", "target": "extract_1" }]
  }
  ```

  ```python Python theme={null}
  import os
  from anyformat.sdk import Client
  from anyformat.workflow import Schema

  client = Client(api_key=os.environ["ANYFORMAT_API_KEY"])

  workflow = (
      client.workflow("Email lead extractor")
      .parse()
      .extract([
          Schema.string("sender_name",  "Full name of the person who sent the email"),
          Schema.string("sender_email", "Email address of the sender"),
          Schema.string("company_name", "Company or organization the sender represents"),
          Schema.enum("inquiry_type", "The primary category of this inquiry", options=[
              Schema.option("pricing",      "Pricing or cost inquiry"),
              Schema.option("demo_request", "Request for a product demo"),
              Schema.option("support",      "Technical support or help request"),
              Schema.option("partnership",  "Partnership or integration inquiry"),
              Schema.option("other",        "Other inquiry type"),
          ]),
          Schema.enum("urgency", "How urgent this request appears based on language and deadlines mentioned", options=[
              Schema.option("low",    "No urgency signals"),
              Schema.option("medium", "Moderate urgency or soft deadline"),
              Schema.option("high",   "Explicit deadline or urgent language"),
          ]),
          Schema.multi_select("topics", "Products or areas of interest mentioned in the email", options=[
              Schema.option("enterprise",  "Enterprise plan or features"),
              Schema.option("integration", "Integration or API capabilities"),
              Schema.option("pricing",     "Pricing or billing"),
              Schema.option("security",    "Security or compliance"),
          ]),
          Schema.string("summary", "A one-sentence summary of what the sender is asking for"),
      ])
      .create()
  )
  ```

  ```typescript TypeScript theme={null}
  import { Anyformat, Schema } from "@anyformat/sdk";

  const af = new Anyformat({ apiKey: process.env.ANYFORMAT_API_KEY! });

  const workflow = await af
    .workflow("Email lead extractor")
    .parse()
    .extract([
      Schema.string("sender_name",  "Full name of the person who sent the email"),
      Schema.string("sender_email", "Email address of the sender"),
      Schema.string("company_name", "Company or organization the sender represents"),
      Schema.enum("inquiry_type", "The primary category of this inquiry", [
        Schema.option("pricing",      "Pricing or cost inquiry"),
        Schema.option("demo_request", "Request for a product demo"),
        Schema.option("support",      "Technical support or help request"),
        Schema.option("partnership",  "Partnership or integration inquiry"),
        Schema.option("other",        "Other inquiry type"),
      ]),
      Schema.enum("urgency", "How urgent this request appears based on language and deadlines mentioned", [
        Schema.option("low",    "No urgency signals"),
        Schema.option("medium", "Moderate urgency or soft deadline"),
        Schema.option("high",   "Explicit deadline or urgent language"),
      ]),
      Schema.multiSelect("topics", "Products or areas of interest mentioned in the email", [
        Schema.option("enterprise",  "Enterprise plan or features"),
        Schema.option("integration", "Integration or API capabilities"),
        Schema.option("pricing",     "Pricing or billing"),
        Schema.option("security",    "Security or compliance"),
      ]),
      Schema.string("summary", "A one-sentence summary of what the sender is asking for"),
    ])
    .create();
  ```
</CodeGroup>

## Run and read

Send the email body as a text file. Any `.txt` works; a saved `.eml` works too and keeps the headers.

<CodeGroup>
  ```bash curl theme={null}
  # The body of the email, saved as a file
  curl -X POST "https://api.anyformat.ai/v3/workflows/$WORKFLOW_ID/upload/run/" \
    -H "Authorization: Bearer $ANYFORMAT_API_KEY" \
    -F 'files=@lead-email.txt;type=text/plain'
  # -> 202 {"run_id": "...", "status": "queued"}

  # Read the run. Repeat until "status" is "processed".
  curl "https://api.anyformat.ai/v3/runs/$RUN_ID/" \
    -H "Authorization: Bearer $ANYFORMAT_API_KEY"
  ```

  ```python Python theme={null}
  email_text = """From: Maria Lopez <maria.lopez@acmecorp.example>
  Subject: Enterprise pricing for Q2 rollout

  Hi, I am the VP of Engineering at Acme Corp. We process about 40,000 supplier
  invoices a month ... Our board meets on June 12 and I need a quote and a
  security overview before then. ..."""

  # text= uploads the string as a .txt file for you
  result = workflow.run(text=email_text).wait()

  lead = {name: field.value for name, field in result.fields.items()}
  if lead["urgency"] == "high":
      print(f"HIGH PRIORITY: {lead['sender_name']} from {lead['company_name']}")
  ```

  ```typescript TypeScript theme={null}
  import type { ExtractedField } from "@anyformat/sdk";

  const emailText = `From: Maria Lopez <maria.lopez@acmecorp.example>
  Subject: Enterprise pricing for Q2 rollout

  Hi, I am the VP of Engineering at Acme Corp. ...`;

  const file = new File([emailText], "lead-email.txt", { type: "text/plain" });
  const run = await workflow.run(file);
  const result = await run.wait();

  // field() can return a scalar or rows; every field here is a scalar
  const field = (name: string) => result.field(name) as ExtractedField<string> | undefined;
  if (field("urgency")?.value === "high") {
    console.log(`HIGH PRIORITY: ${field("sender_name")?.value} from ${field("company_name")?.value}`);
  }
  ```
</CodeGroup>

## What comes back

The run above, trimmed. It took 10 seconds. Every field also carries `verification_status` and `value_override`; only the first shows them here.

```json theme={null}
{
  "id": "06a8d903-aa8e-7da7-8000-1f5d790c85f7",
  "status": "processed",
  "results": {
    "document_packet_id": "06a8d903-a825-759d-8000-1ebf38d10ce5",
    "verification_url": "https://app.anyformat.ai/workflows/.../files/...",
    "parse": { "markdown": "<a id=\"md_p1_b0\"></a>\n\nFrom: Maria Lopez <maria.lopez@acmecorp.example> ...", "blocks": [] },
    "classifications": [],
    "splits": [],
    "extractions": [
      {
        "split_name": null,
        "partition": null,
        "fields": {
          "sender_name":  { "value": "Maria Lopez", "confidence": 99.0, "verification_status": "not_verified", "value_override": null,
                            "evidence": [{ "text": "From: Maria Lopez <maria.lopez@acmecorp.example>", "page_number": 1 }] },
          "sender_email": { "value": "maria.lopez@acmecorp.example", "confidence": 99.0, "evidence": [ "..." ] },
          "company_name": { "value": "Acme Corp", "confidence": 99.0, "evidence": [{ "text": "I am the VP of Engineering at Acme Corp.", "page_number": 1 }] },
          "inquiry_type": { "value": "pricing", "confidence": 98.0, "evidence": [{ "text": "1. What does the enterprise plan cost at our volume?", "page_number": 1 }] },
          "urgency":      { "value": "high", "confidence": 99.0, "evidence": [{ "text": "Our board meets on June 12 and I need a quote and a security overview before then.", "page_number": 1 }] },
          "topics":       { "value": "enterprise, pricing, security, integration", "confidence": 98.0, "evidence": [ "..." ] },
          "summary":      { "value": "Maria Lopez is requesting an enterprise-volume quote, security overview including SSO and SOC 2 report, and ERP API integration information before June 12.", "confidence": 4.0, "evidence": [ "..." ] }
        }
      }
    ],
    "edits": []
  }
}
```

## Tips

* A `multi_select` value comes back as one comma-separated string: `"enterprise, pricing, security, integration"`. Split on `", "` to get the list.
* Expect a low confidence on a generated `summary`. The sentence is not quoted from the email, so it scores low even when it is right. Gate on the extracted fields, not on the summary.
* Write `enum_options` descriptions that separate the close cases. `pricing` and `demo_request` are easy to confuse without them.
* A `.txt` upload has no layout, so `parse.blocks` is empty and `parse_confidence` is `null`. That is expected for text input.
* To process a mailbox, create the workflow once and call upload/run per email. The submission limit is 60 requests per minute.

## Next steps

<CardGroup cols={2}>
  <Card title="Extract node" icon="table-list" href="/guides/nodes/extract">
    Field types, modes and smart lookup
  </Card>

  <Card title="Upload and run" icon="play" href="/api-reference-v3/workflows/upload-and-run">
    Multipart fields, conflicts and idempotency
  </Card>

  <Card title="Classify node" icon="tags" href="/guides/nodes/classify">
    Route each email type to its own Extract node
  </Card>

  <Card title="Field types" icon="list" href="/concepts/field-types">
    Every data type a field can take
  </Card>
</CardGroup>
