Build an AI Email Summarizer API

Call the Claude API from Python to build a FastAPI email summarizer.

Introduction

30 Second Summary

Every day, your inbox fills with emails that bury important tasks inside walls of text. Imagine having a tool that instantly pulls out what matters and tells you exactly what you need to do.

In this project, you will build a Python API that accepts email text and returns AI-generated summaries with extracted action items, powered by Claude.

What You'll Build

A local API service that sends email text to Claude and returns structured JSON with a summary and extracted action items.

By the end of this project, you'll have:

  • A working Python script that calls the Claude API to process natural language text.
  • A running FastAPI server with a POST endpoint that accepts email text and returns structured JSON.
  • Auto-generated interactive API documentation you can use to test your endpoints directly in the browser.
  • Secret Mission: Add a second endpoint that categorizes emails by urgency.

Are there any prerequisites?

You need an Anthropic Console account with an API key and credits. You also need Python 3.14.2 and VS Code or Cursor installed on your machine.

Set Up Your Project Environment

Before writing any code, you need a clean workspace. A dedicated project folder and virtual environment keep your project's dependencies isolated from everything else on your machine.

In this step, you'll create the project folder, set up a Python virtual environment, install the packages you need, and configure your API key so the Claude SDK can authenticate.

In this step, get ready to:
  • Create a project folder and Python virtual environment.
  • Install the Anthropic SDK and FastAPI framework.
  • Set your API key as an environment variable.
Create a project folder and virtual environment

A virtual environment is a self-contained Python workspace. Packages you install inside it stay isolated from other projects on your machine.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Open a new terminal by clicking Terminal in the top menu bar, then clicking New Terminal.

What terminal will I see?

On macOS, your terminal uses zsh or bash. On Windows, VS Code defaults to PowerShell. Both work for this project. The commands below are written for each platform.

  • Move to your Desktop by running this command:

macOS

cd ~/Desktop

Windows

cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir ai-email-summarizer
cd ai-email-summarizer
  • Create a Python virtual environment and activate it by running:

macOS

python3 -m venv .venv
source .venv/bin/activate

Windows

python -m venv .venv
.venv\Scripts\Activate

Your terminal prompt should now start with (.venv). This confirms your virtual environment is active.

What is a virtual environment?

Think of it like a sandbox. Packages you install inside .venv stay contained in this project. They won't conflict with other Python projects on your machine.

The (.venv) prefix in your prompt tells you the sandbox is active. Any pip install commands you run will install packages into this environment only.

Don't see (.venv) in your prompt?

Make sure you ran the activation command for your operating system. On macOS, it starts with source. On Windows PowerShell, it ends with \Scripts\Activate.

If you see an error about execution policies on Windows, run Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned first, then try the activation command again.

Help me activate my Python virtual environment.

Install the required packages

With your virtual environment active, you can now install the two packages this project depends on.

  • Install the project dependencies by running:
pip install anthropic "fastapi[standard]"

What do these packages do?

  • anthropic is the official Python SDK for calling the Claude API.
  • fastapi[standard] installs the FastAPI web framework plus uvicorn (the server) and the fastapi CLI for running your app.
  • Confirm the packages installed by running:
pip list

You should see anthropic and fastapi listed in the output along with their version numbers.

Seeing a "pip not found" error?

This usually means your virtual environment is not active. Check that your prompt starts with (.venv). If it does not, re-run the activation command from the previous substep.

Help me fix my pip install error.

Set your API key as an environment variable

The Anthropic SDK looks for an environment variable called ANTHROPIC_API_KEY when it connects to the Claude API. Setting this in your terminal means you never have to paste your key directly into your code.

  • Set your API key by running this command (replace your-api-key-here with your actual key from the Anthropic Console):

macOS

export ANTHROPIC_API_KEY=your-api-key-here

Windows

$env:ANTHROPIC_API_KEY="your-api-key-here"

Why use an environment variable?

Environment variables keep secrets out of your source code. The Anthropic SDK reads ANTHROPIC_API_KEY automatically, so you never have to hardcode your key in a Python file.

  • Verify the key is set by running:

macOS

echo $ANTHROPIC_API_KEY

Windows

echo $env:ANTHROPIC_API_KEY

You should see your API key printed in the terminal. If you see a blank line, re-run the export command above.

This variable is session-only

If you close this terminal, you will need to set ANTHROPIC_API_KEY again next time. For this project, that is fine. Keep the terminal open throughout.

Key prints blank or shows an error?

Make sure there are no spaces around the = sign in your command. On macOS, the command starts with export. On Windows PowerShell, the variable name is prefixed with $env: and the value is wrapped in quotes.

If you are using Command Prompt (CMD) on Windows instead of PowerShell, use set ANTHROPIC_API_KEY=your-api-key-here instead.

Help me set my Anthropic API key as an environment variable.

Your environment is ready. Next up, you'll write your first Python script that calls the Claude API and gets back a real AI-generated response.

Write Your First Claude API Call

Your development environment is ready with all the tools installed. Now you'll write Python code that sends text to Claude and gets an AI-generated response back.

The Anthropic SDK makes this surprisingly simple. Before building a full API, you need to verify your connection to Claude works and shape its responses into something useful.

In this step, get ready to:
  • Test that your API key works by sending a request to Claude.
  • Build a reusable function that summarizes emails and extracts action items.
  • Run your function on a sample email to see structured output.
Create a test script

Before building anything complex, you'll send a single message to Claude and confirm it responds. This verifies your API key and SDK are working correctly.

  • In VS Code, create a new file called test_claude.py inside your ai-email-summarizer folder.
  • Paste the following code into test_claude.py:
import anthropic

# Create a client that reads your API key from the environment
client = anthropic.Anthropic()

# Send a message to Claude and store the response
message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": "Summarize this in one sentence: The quarterly report shows revenue increased by 15% compared to last year, driven primarily by the new product line launched in March.",
        }
    ],
)

# Print Claude's response text
print(message.content[0].text)

What does this code do?

  • import anthropic loads the Anthropic SDK you installed in Step 1.
  • anthropic.Anthropic() creates a client that automatically reads your ANTHROPIC_API_KEY environment variable.
  • client.messages.create(...) sends a request to Claude with a model name, a token limit, and your message.
  • message.content[0].text extracts the text from Claude's response object.
  • Save test_claude.py.
Run the script and understand the response
  • Run your test script by entering this command in the terminal:
python test_claude.py

You should see a one-sentence summary of the quarterly report printed in your terminal.

Seeing an error instead of a summary?

  • If you see AuthenticationError, your API key is missing or incorrect. Re-run the set command from Step 1 to set ANTHROPIC_API_KEY again.
  • If you see ModuleNotFoundError, make sure your virtual environment is active. Your terminal prompt should start with (.venv).
  • If you use PowerShell instead of CMD, set the key with $env:ANTHROPIC_API_KEY = "your-api-key-here" instead.

help me fix my Claude API authentication error

Turn it into a reusable function

A one-off script is useful for testing, but real projects need reusable code. You'll now wrap the API call in a function that accepts any email text and returns a formatted summary with action items.

  • Select all the code in test_claude.py and replace it with the following. This is the import, client setup, and your new summarize_text function:
import anthropic

client = anthropic.Anthropic()


def summarize_text(text: str) -> str:
    # Build a prompt that tells Claude exactly how to format its response
    prompt = f"""Summarize the following email and extract any action items.

Email:
{text}

Respond in this exact format:
SUMMARY: [your summary here]
ACTION ITEMS:
- [action item 1]
- [action item 2]
(if no action items, write "None")"""

    # Send the prompt to Claude
    message = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        messages=[{"role": "user", "content": prompt}],
    )

    # Return just the text of Claude's response
    return message.content[0].text

What does this code do?

  • def summarize_text(text: str) -> str: defines a function with a type hint. It takes a string in and returns a string out.
  • The f-string (f"""...""") is a multi-line template that inserts the text variable into the prompt using {text}.
  • The prompt tells Claude exactly how to format its response, making the output predictable and parseable.
  • Below the function, add this code to test it with a sample email:
sample_email = """Hi team,

Just a reminder that the project deadline is this Friday. Please make sure to:
1. Submit your final code reviews by Wednesday
2. Update the documentation
3. Send me your time sheets for the month

Let me know if you have any blockers.

Thanks,
Sarah"""

result = summarize_text(sample_email)
print(result)

What does this code do?

  • sample_email is a multi-line string (using triple quotes) that holds a realistic email for testing.
  • summarize_text(sample_email) calls your function and stores the result in a variable.
  • print(result) displays Claude's formatted summary and action items in the terminal.
  • Save test_claude.py.
  • Run the updated script:
python test_claude.py

You should see a SUMMARY: line followed by an ACTION ITEMS: section listing three items extracted from the email.

Not seeing a formatted summary?

  • Make sure you replaced ALL the code in test_claude.py. The file should start with import anthropic and end with print(result).
  • If you see a response but it is not in the SUMMARY/ACTION ITEMS format, that is normal. Claude may vary its output slightly, but you should still see both a summary and action items.

help me debug my summarize_text function

✔️ Awesome, I've got everything!

Great. Double check you've saved test_claude.py and move on to the next step.

ⓧ I'd like to double check the full code

Here is the complete test_claude.py file. Make sure yours matches exactly:

import anthropic

client = anthropic.Anthropic()


def summarize_text(text: str) -> str:
    prompt = f"""Summarize the following email and extract any action items.

Email:
{text}

Respond in this exact format:
SUMMARY: [your summary here]
ACTION ITEMS:
- [action item 1]
- [action item 2]
(if no action items, write "None")"""

    message = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        messages=[{"role": "user", "content": prompt}],
    )

    return message.content[0].text


sample_email = """Hi team,

Just a reminder that the project deadline is this Friday. Please make sure to:
1. Submit your final code reviews by Wednesday
2. Update the documentation
3. Send me your time sheets for the month

Let me know if you have any blockers.

Thanks,
Sarah"""

result = summarize_text(sample_email)
print(result)

Your Python script can now call Claude and get structured responses back. Next up, you'll wrap this logic in a proper API so other applications can call it too.

Build the FastAPI App

Your test_claude.py script proved that Python can talk to Claude and get back structured responses. Now it's time to wrap that logic in a proper API that other applications can call.

An API gives your summarizer a URL that any program can send requests to. FastAPI is a Python web framework that makes building APIs fast. It uses type hints and Pydantic models to define what data goes in and what comes back, and it auto-generates interactive documentation for testing.

In this step, get ready to:
  • Create a FastAPI application file with structured data models.
  • Build a POST endpoint that calls Claude and returns parsed results.
  • Start your API server and confirm it is running.
Create the API application file

You will build your API in a new file called main.py. This file defines the data shapes, sets up the server, and contains the endpoint that processes emails.

  • Create a new file called main.py in your ai-email-summarizer folder.
  • Add the imports, app instance, and Anthropic client by pasting this code into main.py:
import anthropic
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI(
    title="AI Email Summarizer",
    description="An API that uses Claude to summarize emails and extract action items.",
)

client = anthropic.Anthropic()

What does this code do?

  • import anthropic brings in the Claude SDK you already used in test_claude.py.
  • FastAPI is the web framework class. HTTPException lets you return proper error responses with status codes.
  • BaseModel from Pydantic defines the shape of data your API accepts and returns.
  • app = FastAPI(...) creates your API application with a title and description that appear in the auto-generated docs.

You won't see a change yet. The next chunk defines the data models and a health-check endpoint.

  • Below the client = anthropic.Anthropic() line, add the Pydantic models and a basic GET endpoint:


class SummarizeRequest(BaseModel):
    text: str
    max_length: str = "brief"


class SummarizeResponse(BaseModel):
    summary: str
    action_items: list[str]
    word_count: int


@app.get("/")
def read_root():
    return {"message": "AI Email Summarizer API is running"}

What does this code do?

  • SummarizeRequest defines what data callers must send. text is required. max_length defaults to "brief" if not provided.
  • SummarizeResponse defines what your API sends back. FastAPI enforces this shape automatically.
  • @app.get("/") is a decorator that maps the root URL to the read_root function. This gives you a quick health check to confirm the server is running.

You won't see a change yet. The next chunk adds the main endpoint that calls Claude.

  • Below the read_root function, add the POST endpoint that summarizes emails:


@app.post("/summarize", response_model=SummarizeResponse)
def summarize_email(request: SummarizeRequest):
    prompt = f"""Summarize the following email and extract any action items.
Length preference: {request.max_length}

Email:
{request.text}

Respond in this exact format:
SUMMARY: [your summary here]
ACTION ITEMS:
- [action item 1]
- [action item 2]
(if no action items, write "None")"""

    try:
        message = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            messages=[{"role": "user", "content": prompt}],
        )

        response_text = message.content[0].text

        parts = response_text.split("ACTION ITEMS:")
        summary = parts[0].replace("SUMMARY:", "").strip()

        action_items = []
        if len(parts) > 1:
            items_text = parts[1].strip()
            if items_text.lower() != "none":
                action_items = [
                    item.strip().lstrip("- ")
                    for item in items_text.split("\n")
                    if item.strip() and item.strip() != "-"
                ]

        return SummarizeResponse(
            summary=summary,
            action_items=action_items,
            word_count=len(request.text.split()),
        )

    except Exception as e:
        raise HTTPException(status_code=500, detail=f"AI processing failed: {str(e)}")

What does this code do?

  • @app.post("/summarize", response_model=SummarizeResponse) maps the /summarize URL to this function and tells FastAPI to validate the response shape.
  • The f-string prompt includes the caller's email text and their length preference.
  • response_text.split("ACTION ITEMS:") splits Claude's response into two halves so you can extract the summary and action items separately.
  • The list comprehension filters blank lines and strips the leading - from each action item.
  • The try/except block catches any failure from the Claude API call and returns a 500 error with a helpful message instead of crashing the server.
  • Save main.py.

Seeing a syntax error when you save?

  • Check that the triple-quoted f-string (f"""...""") has matching triple quotes at the start and end.
  • Make sure the except block is indented at the same level as try.
  • Confirm the list comprehension brackets [...] are closed properly.

Ask for help: Why am I getting a syntax error in my main.py FastAPI file?

✔️ Awesome, I've got everything!

Great. Double check you've saved main.py before moving on.

ⓧ I'd like to double check the full code

Here is the complete main.py file. Compare yours against it:

import anthropic
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI(
    title="AI Email Summarizer",
    description="An API that uses Claude to summarize emails and extract action items.",
)

client = anthropic.Anthropic()


class SummarizeRequest(BaseModel):
    text: str
    max_length: str = "brief"


class SummarizeResponse(BaseModel):
    summary: str
    action_items: list[str]
    word_count: int


@app.get("/")
def read_root():
    return {"message": "AI Email Summarizer API is running"}


@app.post("/summarize", response_model=SummarizeResponse)
def summarize_email(request: SummarizeRequest):
    prompt = f"""Summarize the following email and extract any action items.
Length preference: {request.max_length}

Email:
{request.text}

Respond in this exact format:
SUMMARY: [your summary here]
ACTION ITEMS:
- [action item 1]
- [action item 2]
(if no action items, write "None")"""

    try:
        message = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            messages=[{"role": "user", "content": prompt}],
        )

        response_text = message.content[0].text

        parts = response_text.split("ACTION ITEMS:")
        summary = parts[0].replace("SUMMARY:", "").strip()

        action_items = []
        if len(parts) > 1:
            items_text = parts[1].strip()
            if items_text.lower() != "none":
                action_items = [
                    item.strip().lstrip("- ")
                    for item in items_text.split("\n")
                    if item.strip() and item.strip() != "-"
                ]

        return SummarizeResponse(
            summary=summary,
            action_items=action_items,
            word_count=len(request.text.split()),
        )

    except Exception as e:
        raise HTTPException(status_code=500, detail=f"AI processing failed: {str(e)}")
Understand your API's key patterns

Why FastAPI instead of Flask?

FastAPI uses Python type hints and Pydantic models to define your API's contract. This means you declare what data goes in and what comes back, and FastAPI automatically validates requests, generates documentation, and returns clear error messages.

Flask is more flexible but requires you to handle validation manually. For AI-powered APIs where structured input and output matter, FastAPI's approach catches bugs early and teaches modern Python patterns you will use in production.

What is a Pydantic BaseModel?

A BaseModel class defines the exact shape of data. Think of it as a contract: SummarizeRequest says "callers must send a text string and may optionally include max_length." If a caller sends the wrong type, FastAPI rejects the request before your code runs.

Start the server
  • Start the FastAPI development server by running this command in your terminal:
fastapi dev main.py

What does this command do?

The fastapi dev command reads your main.py file, finds the app object, and starts a local web server using Uvicorn. It enables auto-reload, so any changes you save will restart the server automatically.

  • Look at your terminal output. You should see a line that says Uvicorn running on http://127.0.0.1:8000 and Application startup complete.
  • Open your browser and go to http://127.0.0.1:8000. You should see the JSON response {"message":"AI Email Summarizer API is running"}.

Server not starting?

  • If you see ModuleNotFoundError, make sure your virtual environment is active (you should see (.venv) in your prompt).
  • If you see a syntax error with a file and line number, open main.py and check the line mentioned for missing colons, unmatched brackets, or indentation issues.
  • If port 8000 is already in use, close any other servers running on that port and try again.

Ask for help: Why won't my FastAPI dev server start?

Your API server is live and responding. Next up, you'll test the /summarize endpoint through the interactive documentation and see your structured responses in action.

Test Your API

Your API app is built and your server is already running. The /summarize endpoint is ready to process emails.

In this step, you'll use FastAPI's auto-generated documentation to test your endpoint. This interactive tool lets you send requests and see responses right in your browser.

In this step, get ready to:
  • Explore FastAPI's interactive API documentation.
  • Test the /summarize endpoint with a sample email.
  • Understand HTTP status codes and response structure.
Explore the auto-generated documentation

FastAPI automatically creates interactive documentation for every endpoint you define. This documentation lets you test your API directly in the browser without writing any client code.

  • Open your browser and navigate to http://127.0.0.1:8000/docs.

You should see a page titled AI Email Summarizer with your description underneath. Below that, your two endpoints are listed: GET / and POST /summarize.

What is this page?

This is Swagger UI, an interactive documentation tool that FastAPI generates automatically from your code. It reads your Pydantic models and decorators to show the exact request body schema (what data to send) and response schema (what data comes back).

When you work with any professional API in the future, you'll look for docs like this. Your own code just produced the same kind of documentation that teams at major tech companies build for their APIs.

Test the /summarize endpoint

Now you'll send a real request to your API and watch Claude process it.

  • Click on the POST /summarize endpoint to expand it.
  • Click Try it out on the right side.
  • Replace the request body with this JSON:
{
  "text": "Hi team, the client meeting is moved to Thursday at 2pm. Please prepare your slides by Wednesday evening and send them to me for review. Also, remember to book the conference room.",
  "max_length": "brief"
}
  • Click Execute.

After a moment, a response appears below the request body. You should see a JSON response containing three fields: summary, action_items, and word_count.

What should I see?

The summary field contains Claude's condensed version of the email. The action_items field is a list of tasks Claude extracted (like preparing slides and booking the conference room).

The word_count field shows how many words were in the original email. This structured response is exactly what your SummarizeResponse model defined.

Not seeing a response?

If you see a 500 error with a message about authentication, your ANTHROPIC_API_KEY environment variable may not be set in the terminal where the server is running. Stop the server with Ctrl+C, re-set the key, and restart with fastapi dev main.py.

If the page itself does not load, make sure your FastAPI server is still running in your terminal. Look for help with this issue.

Understand HTTP status codes and response structure

Every API response includes an HTTP status code that tells you whether the request succeeded or failed. Your successful response showed status code 200, which means everything worked.

What are HTTP status codes?

Status code 200 means success. Status code 500 means something went wrong on the server side. Your HTTPException in main.py returns a 500 error with a detail message whenever Claude fails to process the request.

The response body matches your SummarizeResponse model exactly. FastAPI validates the output against your model before sending it back, so clients always get predictable, structured data.

Try testing an edge case to see how your API handles unexpected input.

  • Back in the Swagger UI, click Try it out again on the POST /summarize endpoint.
  • Replace the request body with:
{"text": "", "max_length": "brief"}
  • Click Execute.

Notice the response. Claude still processes the request, but the summary for empty text is minimal or generic. In a production API, you would add input validation to reject empty text before it ever reaches Claude.

Secret mission

Add a /categorize Endpoint

Your /summarize endpoint handles one job well. But real email assistants do more than summarize. Can you build a second endpoint that classifies emails by type and urgency?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing cloud costs. The only expense was the Claude API calls you made during testing, which are pay-per-use and stop when you stop sending requests.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • FastAPI dev server running at http://127.0.0.1:8000.
  • The ai-email-summarizer project folder on your Desktop (includes .venv, main.py, and test_claude.py).

Keep everything running

No action needed. Choose this if you are still actively building or want to keep experimenting with your API.

  • Your FastAPI server keeps running until you close the terminal or press Ctrl+C.
  • The ANTHROPIC_API_KEY environment variable only lasts for your current terminal session. If you close and reopen the terminal, you will need to set it again.
  • Claude API costs only accumulate when you send requests. A stopped server costs nothing.

Pause - I'll come back to this later

Shut down running processes to free up memory. Your project files stay on disk for next time.

  • Switch to the terminal where your FastAPI server is running and press Ctrl+C to stop it.
  • Close the terminal. This ends your virtual environment session and clears the ANTHROPIC_API_KEY environment variable.
  • When you come back, reopen your terminal in the ai-email-summarizer folder, reactivate .venv, set your API key again, and run fastapi dev main.py.

Delete - I don't want to use this again

Remove all project files and start fresh. This cannot be undone.

  • Switch to the terminal where your FastAPI server is running and press Ctrl+C to stop it.
  • Deactivate your virtual environment by running:
deactivate
  • Delete the entire project folder. On macOS/Linux, run:
rm -rf ~/Desktop/ai-email-summarizer
  • On Windows, run:
rmdir /s /q %USERPROFILE%\Desktop\ai-email-summarizer
  • This removes main.py, test_claude.py, and the .venv virtual environment (along with all installed packages) in one step.
  • Close the terminal to clear the ANTHROPIC_API_KEY environment variable from the session.

Nice Work!

Nice Work!

You just built an AI-powered API from scratch using Python. Your service accepts raw email text and returns structured summaries, action items, and categorizations powered by Claude.

Here's what you accomplished:

  • Built a Python script that calls the Claude API to process natural language and extract structured information from emails.
  • Created a FastAPI application with Pydantic models that validate request and response data automatically.
  • Exposed your AI logic as a POST endpoint and tested it through auto-generated interactive documentation.
  • Secret Mission: Added a /categorize endpoint that classifies emails by urgency.

Ready to quiz yourself?