~/TechPurAI
~/tutorials/ai-projects-with-python/build-an-ai-email-generator
intermediate·part 16 of 22·3 min read

Build an AI Email Generator

Updated Aug 16, 2026Python · AI

This part applies part 15's on-brand generation techniques to a genuinely distinct, real output format — email, where structure (a real subject line, a real, concise body, a clear call to action) matters as much as tone. The real use case: GreenDesk's sales team following up with actual, real leads.

The real, complete email generator

python
# email_generator.py
import json
from shared.ai_client import get_client
from style_guide import STYLE_GUIDES

def generate_follow_up_email(lead: dict) -> dict:
    client = get_client()
    response = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=512,
        system=(
            f"Write a real, personalized follow-up email for a "
            f"GreenDesk sales lead.\n\n{STYLE_GUIDES['greendesk']}\n\n"
            f"Keep the body under 120 words. Include ONE clear call "
            f"to action. Respond with ONLY valid JSON in this exact "
            f'format: {{"subject": "...", "body": "..."}}'
        ),
        messages=[
            {
                "role": "user",
                "content": (
                    f"Lead name: {lead['name']}\n"
                    f"Company: {lead['company']}\n"
                    f"Team size: {lead['team_size']}\n"
                    f"Interest: {lead['interest']}"
                ),
            }
        ],
    )
    return json.loads(response.content[0].text)

A real, concrete example

python
real_lead = {
    "name": "Priya",
    "company": "Northwind Studios",
    "team_size": "35",
    "interest": "requested a demo of task automation features",
}

email = generate_follow_up_email(real_lead)
print(email["subject"])
print(email["body"])
text
Real, expected output:
Subject: "Following up on your GreenDesk demo request"

Body: "Hi Priya, following up on your interest in GreenDesk's task
automation for Northwind Studios. With a 35-person team, automation
usually saves real, meaningful coordination time. Want to grab 15
minutes this week to see it applied to your actual workflow?"

Notice the real, specific personalization — team size and stated interest both directly shape the email's actual content, not just a name inserted into an otherwise generic template.

Why it matters

This is a genuinely different, real value proposition than a traditional mail-merge template — a template can insert a name and company, but it can't naturally weave in "35-person team" as a real, relevant reason automation matters to that specific lead. That kind of contextual, real personalization is exactly what makes this genuinely worth building with an LLM rather than simpler, existing tools.

Real, structured JSON output, and why it matters here specifically

python
return json.loads(response.content[0].text)

This directly extends part 14's structured-output technique — requesting real JSON with subject and body as separate real fields, rather than one undifferentiated block of text, is what lets this function's output drop directly into a real email-sending function (send_email(to=lead["email"], subject=email["subject"], body=email["body"])) without any manual parsing.

A real, important safeguard: human review before sending

python
def generate_and_queue_for_review(lead: dict) -> None:
    email = generate_follow_up_email(lead)
    save_to_review_queue(lead_id=lead["id"], subject=email["subject"], body=email["body"])
    # a real, human sales rep reviews and approves before it actually sends

This is a real, deliberate design choice, not an oversight — an AI-generated email making a real, incorrect claim about GreenDesk's actual features, or landing with an off tone for a specific real prospect, is a genuinely more consequential mistake than an internal summary or draft blog post, since it's sent directly, externally, under the company's real name. A queue-for-review step, rather than fully automated sending, is the honest, calibrated safeguard for this specific, higher-stakes real use case.

Batch generation for a real list of leads

python
def generate_emails_for_leads(leads: list[dict]) -> list[dict]:
    return [
        {"lead_id": lead["id"], **generate_follow_up_email(lead)}
        for lead in leads
    ]

This directly mirrors part 10's translate_catalog() batch pattern — the same real principle of processing a full, real list of items rather than only handling one at a time, genuinely necessary the moment this tool needs to serve GreenDesk's actual, full pipeline of real leads rather than a single example.

Next: building a real AI code explainer with Python — a genuinely technical use case, explaining real code rather than generating marketing or support content.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous15. Keeping AI-Generated Content On-Brandnext →17. Build an AI Code Explainer with Python