Build an AI Resume Emailer

Use Python and Claude AI to generate and send personalized job application emails.

Introduction

30 Second Summary

Sending job application emails is one of the most repetitive parts of a job search. You rewrite the same cover message over and over, attach your resume, and hope it sounds professional enough.

In this project, you will build a Python command-line tool that reads your resume PDF and turns it into a professional job application email. Claude AI writes the cover message based on your actual experience, and the script sends it with your resume attached.

What You'll Build

Run the script from your terminal, and within seconds a professional email arrives in your recipient's inbox with a personalized cover message and your resume attached as a PDF.

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

  • A professional email that arrives in any inbox with your resume attached and a personalized cover message, all triggered by running a single script in your terminal.
  • An AI-generated cover email that reads your actual resume content and highlights your specific skills and accomplishments in natural, professional language.
  • A reusable command-line tool you can use for real job applications whenever you need to send out your resume quickly.
  • Secret Mission: Feed in a job description so Claude tailors the email to a specific role, referencing the company name and job requirements.

Are there any prerequisites?

You need a Gmail account with 2-Step Verification enabled. This is required to generate an App Password for sending emails from your script.

You also need an Anthropic Console account at platform.claude.com. New accounts receive free credits that cover this entire project.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters to you. This is your moment to commit to the goal before any hands-on work begins.

Set Up Your Tools and Credentials

Your script will talk to two external services: Claude for generating email content, and Gmail's SMTP server for sending emails. Both of these services require credentials to accept your requests.

In this step, you'll install the Python packages your project depends on and set up the API key and app password that let your code authenticate with Claude and Gmail.

In this step, get ready to:
  • Install the anthropic and pypdf packages.
  • Set up your Anthropic API key and Gmail App Password as environment variables.
  • Verify everything works with a quick import check.
Install the Python packages
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
  • Install both packages by running this command:
pip install anthropic pypdf

What are these packages?

The anthropic package is the official Python SDK for calling the Claude API. It handles authentication, request formatting, and response parsing for you.

The pypdf package is a pure-Python library for reading PDF files. You'll use it to extract text from your resume so Claude can read it.

  • You should see output ending with lines like Successfully installed anthropic-... pypdf-... confirming both packages are installed.

Seeing 'pip: command not found'?

  • Try pip3 instead of pip. Some systems use pip3 for Python 3.
  • If that doesn't work either, try running python -m pip install anthropic pypdf. This calls pip through your Python installation directly.

Still stuck? Help me fix my pip installation so I can install Python packages.

Set up your API key and app password

Your script needs two credentials to connect to external services. You'll grab an Anthropic API key and a Gmail App Password, then store both as environment variables.

  • Open platform.claude.com in your browser.
  • Sign in to your Anthropic account.
  • Click Account Settings in the sidebar.
  • Select API Keys.
  • Click Create Key.
  • Enter a name like Resume Emailer.
  • Copy the key value that appears.

Your API key is shown once

Copy your API key and paste it somewhere safe right now. You won't be able to see the full key again after closing this page.

  • Set your API key as an environment variable by running this command:

macOS

export ANTHROPIC_API_KEY=your-api-key-here
  • Replace your-api-key-here with the key you just copied from the Anthropic Console.

Windows

If you're using Command Prompt:

set ANTHROPIC_API_KEY=your-api-key-here

If you're using PowerShell:

$env:ANTHROPIC_API_KEY="your-api-key-here"
  • Replace your-api-key-here with the key you just copied from the Anthropic Console.

Now set up your Gmail App Password. App Passwords let your Python script send emails through Gmail without using your main Google password.

Don't have 2-Step Verification enabled?

App Passwords require 2-Step Verification. Click on 2-Step Verification on the security page and follow the prompts to enable it before continuing.

  • Open the App Passwords page at myaccount.google.com/apppasswords. You may need to sign in again.
  • Enter a name like Python Emailer in the app name field.
  • Click Create.
  • Copy the 16-digit password that appears on screen.
  • Set your app password as an environment variable by running this command:

macOS

export GMAIL_APP_PASSWORD=your-app-password-here
  • Replace your-app-password-here with the 16-digit password you just copied from Google.

Windows

If you're using Command Prompt:

set GMAIL_APP_PASSWORD=your-app-password-here

If you're using PowerShell:

$env:GMAIL_APP_PASSWORD="your-app-password-here"
  • Replace your-app-password-here with the 16-digit password you just copied from Google.

Environment variables are session-only

These environment variables only last as long as your terminal session is open. If you close your terminal, you'll need to set them again before running the script.

Can't find App Passwords in your Google Account?

  • Make sure 2-Step Verification is fully enabled, not just started. Go back to the security page and confirm it shows as On.
  • If you're using a work or school Google account, your administrator may have disabled App Passwords. Try with a personal Gmail account instead.
  • If your account uses security keys as the only 2-Step Verification method, App Passwords are not available. Add a second verification method like a phone number first.

Still stuck? Help me set up a Gmail App Password for my Python script.

Verify your setup

Both packages are installed and both credentials are stored as environment variables. Before you move on, what do you think will happen if you try to import both packages in one line?

  • Confirm everything is in place by running this command:
python -c "import anthropic; import pypdf; print('Ready!')"

You should see Ready! printed to your terminal. This confirms both packages are installed and importable.

Not seeing 'Ready!'?

  • If you see ModuleNotFoundError, one of the packages didn't install correctly. Re-run pip install anthropic pypdf and check the output for errors.
  • If you see command not found, try python3 instead of python. Some systems require the full python3 command.
  • Make sure you're running the command in the same terminal session where you installed the packages.

Still stuck? Help me troubleshoot my Python package imports.

Your tools are installed and your credentials are ready. Next up, you'll write the code that sends an email with your resume attached through Gmail.

Send a Basic Email with Your Resume Attached

Your tools and credentials are ready. Now it's time to prove the most important piece of plumbing works: actually sending an email from Python.

Before adding any AI, you need to confirm that your script can connect to Gmail's SMTP server, authenticate, and deliver an email with a PDF attached. If email sending breaks, you want to know now, not after you've wired up Claude.

In this step, get ready to:
  • Create emailer.py with a function that sends emails through Gmail's SMTP server.
  • Attach your resume PDF to the outgoing email.
  • Send a test email to yourself and verify it arrives with the attachment.
Create the email sending function

Python has built-in modules for composing and sending emails. smtplib handles the connection to a mail server, and the email.mime modules let you build a properly formatted email message with headers, body text, and attachments.

  • Create a new file called emailer.py in your code editor and paste in the following code:
import os
import sys
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders

import anthropic
from pypdf import PdfReader


def send_email(sender_email, recipient_email, subject, body, pdf_path):
    # Read the app password from environment variables
    app_password = os.environ["GMAIL_APP_PASSWORD"]

    # Build the email message with headers
    msg = MIMEMultipart()
    msg["From"] = sender_email
    msg["To"] = recipient_email
    msg["Subject"] = subject

    # Add the body text to the email
    msg.attach(MIMEText(body, "plain"))

    # Connect to Gmail's SMTP server and send
    with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
        server.login(sender_email, app_password)
        server.sendmail(sender_email, recipient_email, msg.as_string())

    print(f"Email sent to {recipient_email}!")

What does this code do?

  • The imports at the top pull in Python's built-in email and SMTP modules, plus the anthropic and pypdf packages you installed earlier. You won't use those two until later steps, but importing them now catches missing-package errors early.
  • os.environ["GMAIL_APP_PASSWORD"] reads your Gmail App Password from the environment variable you set in Step 1.
  • MIMEMultipart() creates an email container that can hold multiple parts (text body, attachments). The From, To, and Subject headers tell the mail server who the email is for.
  • SMTP_SSL("smtp.gmail.com", 465) opens a secure SSL connection to Gmail's mail server on port 465. The with block ensures the connection closes cleanly after sending.

This function takes all the email details as arguments, but you need a way to call it. The next block adds a test runner at the bottom of the file.

  • At the bottom of emailer.py, below the send_email function, add this test runner:
if __name__ == "__main__":
    send_email("you@gmail.com", "you@gmail.com", "Job Application", "Please find my resume attached.", "resume.pdf")
  • Replace both instances of you@gmail.com with your actual Gmail address.
  • Save emailer.py.

Why send to yourself?

Using your own email as both sender and recipient means you can instantly check your inbox to confirm the email arrived. No need to coordinate with someone else during testing.

Before you run this, what do you think will happen? The function sends a plain text email with no attachment code yet.

  • Run the script by executing this command in your terminal:
python emailer.py

You should see Email sent to you@gmail.com! printed in your terminal (with your actual email address).

  • Open your email inbox and find the new email with the subject Job Application.

The email body reads "Please find my resume attached." but there is no attachment yet. That is expected. You just proved the email plumbing works.

Email not arriving?

  • Check your Spam or Junk folder. Gmail sometimes filters emails you send to yourself.
  • If you see KeyError: 'GMAIL_APP_PASSWORD', your environment variable is not set in this terminal session. Re-run the export command from Step 1.
  • If you see an authentication error, make sure you are using your 16-character App Password, not your regular Gmail password. App Passwords require 2-Step Verification to be enabled on your Google Account.
  • Make sure the sender email in your code matches the Gmail account you generated the App Password for.

Help me debug why my Python script can't send an email through Gmail SMTP

Add the resume attachment

The email works, but it promises an attached resume and delivers nothing. Time to fix that by reading the PDF file as raw bytes and encoding it as a MIME attachment.

Have a PDF ready

You need a PDF file to test with. Use your actual resume if you have one, or any PDF file you have on hand. Copy it into the same folder as emailer.py and name it resume.pdf (or update the filename in the test runner at the bottom of your script to match your file's name).

  • In emailer.py, find this line inside the send_email function:
    msg.attach(MIMEText(body, "plain"))
  • Add the following attachment code directly below that line, before the with smtplib.SMTP_SSL block:
    # Read the PDF file and attach it to the email
    filename = os.path.basename(pdf_path)
    with open(pdf_path, "rb") as f:
        part = MIMEBase("application", "octet-stream")
        part.set_payload(f.read())
    encoders.encode_base64(part)
    part.add_header("Content-Disposition", f"attachment; filename={filename}")
    msg.attach(part)

What does this code do?

  • os.path.basename(pdf_path) extracts just the filename (like resume.pdf) from a full file path, so the attachment has a clean name in the recipient's inbox.
  • MIMEBase("application", "octet-stream") creates a generic binary attachment container. The file is read as raw bytes ("rb"), then encode_base64 converts the bytes into a text-safe format that email servers can transmit.
  • The Content-Disposition header tells the recipient's email client to display the file as a downloadable attachment rather than inline content.
  • Save emailer.py.
  • Confirm your PDF file is in the same folder as emailer.py by running:
ls

You should see both emailer.py and your resume PDF listed in the output.

Before you run the script again, do you think the email will look any different this time? What specifically should change?

  • Run the script again by executing:
python emailer.py
  • Open your email inbox and find the newest email with the subject Job Application.

This time, the email has your resume PDF attached. The body still says "Please find my resume attached." as placeholder text. The email plumbing is fully working.

Email arrives but no attachment?

  • Make sure the attachment code block is placed after msg.attach(MIMEText(body, "plain")) and before the with smtplib.SMTP_SSL block. The indentation must match the rest of the function body (4 spaces).
  • If you see FileNotFoundError, the PDF file path in the test runner does not match your actual file. Check the filename and make sure the file is in the same folder as emailer.py.

Help me figure out why my email attachment is not working in Python

✔️ Awesome, I've got everything!

Great. Double check you have saved emailer.py and received the test email with your resume PDF attached.

ⓧ I'd like to double check the full code

Scroll up and confirm your emailer.py has these sections in order:

  • Imports block: os, sys, smtplib, the four email.mime imports, encoders, anthropic, and PdfReader.
  • send_email function: reads the app password, builds the MIMEMultipart message with headers and body text, attaches the PDF as a base64-encoded MIMEBase part, connects via SMTP_SSL on port 465, and sends the email.
  • Test runner: if __name__ == "__main__" block calling send_email with your Gmail address, "Job Application" as the subject, placeholder body text, and your resume PDF path.

Your email pipeline is live. You can send emails with attachments from a Python script. Next up, you will extract the actual text content from your resume PDF so Claude can read it and write something worth sending.

Extract Text from Your Resume PDF

In the last step, you proved that your script can send an email with your resume attached through Gmail's SMTP server. The delivery pipeline is working.

Here's the gap: Claude AI needs to read your resume to write a good email, but it takes text, not PDF files. You need to extract the text content from the PDF so you can feed it to Claude in the next step.

In this step, get ready to:
  • Write a function that extracts text from a PDF resume using pypdf.
  • Test the extraction by printing your resume content to the terminal.
Write the PDF text extraction function

Your script already imports PdfReader from pypdf at the top of emailer.py. Now you need a function that uses it to open a PDF, loop through every page, and collect all the text.

  • In emailer.py, find the blank line between the import statements and the send_email function.
  • Add the following extract_resume_text function in that space:
def extract_resume_text(pdf_path):
    # Open the PDF and load its pages
    reader = PdfReader(pdf_path)
    text = ""
    # Loop through each page and pull out the text
    for page in reader.pages:
        page_text = page.extract_text()
        if page_text:
            text += page_text + "\n"
    # Remove extra whitespace from the start and end
    return text.strip()

What does this code do?

  • PdfReader(pdf_path) opens the PDF file and loads its pages into memory.
  • The for page in reader.pages: loop visits each page and calls page.extract_text() to pull the text content out.
  • The if page_text: check skips any pages that return no text, like pages that are purely images.
  • text.strip() cleans up extra whitespace at the start and end before returning the result.
  • Save emailer.py.
Test the extraction with your resume

Your new function is ready, but nothing calls it yet. The if __name__ == "__main__": block at the bottom of emailer.py still calls send_email. You need to swap it out so the script extracts and prints your resume text instead.

  • Scroll to the bottom of emailer.py and find this code:
if __name__ == "__main__":
    send_email("you@gmail.com", "you@gmail.com", "Job Application", "Please find my resume attached.", "resume.pdf")
  • Replace everything inside the if __name__ == "__main__": block so it looks like this:
if __name__ == "__main__":
    resume_text = extract_resume_text("resume.pdf")
    print(resume_text)
  • Save emailer.py.

Before you run the script, think about what you expect to see in the terminal. What information does your resume contain?

  • Run the script by running this command:
python emailer.py

Your resume content prints as plain text to the terminal. You should be able to read your name, skills, and work experience in the output.

Not seeing any text?

  • Make sure your resume PDF is in the same folder as emailer.py. If your file has a different name, update "resume.pdf" in the __main__ block to match your actual filename.
  • If the output is empty or garbled, your PDF may be image-based (a scan rather than typed text). Try a different resume PDF that was created from a word processor.
  • Check that the extract_resume_text function is placed before send_email in the file. Python reads top to bottom, so the function must be defined before it is called.
  • Still stuck? Help me debug why my PDF text extraction returns empty output.

✔️ Awesome, I've got everything!

Great. Make sure you have saved emailer.py.

ⓧ I'd like to double check the full code

Your emailer.py should contain these sections in order. Scroll up and confirm each one is present:

  • The import block at the top (importing os, sys, smtplib, the email modules, anthropic, and PdfReader).
  • The extract_resume_text function (new in this step).
  • The send_email function (unchanged from the previous step).
  • The if __name__ == "__main__": block calling extract_resume_text and print (updated in this step).

Your script can now read your resume and pull out all the text. Next up, you will send that text to Claude AI and get a professional cover email written for you.

Generate a Cover Email with Claude AI

In the last step, you extracted your resume's text content and printed it to the terminal. That text is exactly what Claude needs to write a personalized email for you.

Right now, the email your script sends has a boring placeholder body. In this step, you'll connect to the Claude API and have it read your resume text to generate a polished, professional cover email. This is where the tool becomes genuinely useful.

In this step, get ready to:
  • Write a function that calls the Claude API to generate a professional email from your resume text.
  • Update the script to print the AI-generated email so you can review it.
Write the AI email generator function

The anthropic Python SDK makes calling the Claude API straightforward. You create a client, send a message with instructions, and get text back.

  • In emailer.py, find the blank line between the extract_resume_text function and the send_email function.
  • Add the following new function between them:
def generate_email_body(resume_text):
    # Create the Anthropic client (reads ANTHROPIC_API_KEY from environment)
    client = anthropic.Anthropic()

    # Send the resume text to Claude with instructions to write a cover email
    message = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=1024,
        system="You are a professional email writer. Write a concise, professional job application email based on the candidate's resume. The email should highlight the candidate's key strengths and experience. Keep it to 3-4 short paragraphs. Do not include a subject line. Do not include placeholder brackets. Write it ready to send.",
        messages=[
            {
                "role": "user",
                "content": f"Write a professional job application email based on this resume:\n\n{resume_text}",
            }
        ],
    )

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

What does this code do?

anthropic.Anthropic() creates a client that automatically reads your ANTHROPIC_API_KEY environment variable. No need to pass the key manually.

client.messages.create() sends a request to Claude's API. The system parameter tells Claude how to behave (write a professional email, keep it short, no placeholders). The messages list contains the resume text for Claude to work with.

model="claude-haiku-4-5" specifies Claude Haiku 4.5, a fast and affordable model at $1 per million input tokens. Each run costs a fraction of a cent.

message.content[0].text pulls just the generated text from Claude's response object.

Update the script to call Claude

The new function is ready, but the script doesn't call it yet. You need to update the bottom of the file so it extracts your resume text and then passes it to Claude.

  • In emailer.py, scroll to the bottom and find this block:
if __name__ == "__main__":
    resume_text = extract_resume_text("resume.pdf")
    print(resume_text)
  • Replace that entire block with the following code:
if __name__ == "__main__":
    resume_text = extract_resume_text("resume.pdf")
    print(f"Extracted {len(resume_text)} characters from resume.\n")

    # Generate a professional cover email using Claude AI
    email_body = generate_email_body(resume_text)
    print("\n--- Generated Email ---")
    print(email_body)
    print("--- End of Email ---")

What changed here?

Instead of printing the raw resume text, the script now prints a character count to confirm extraction worked. Then it calls generate_email_body() to send your resume to Claude and prints the AI-generated email between separator lines so you can review it.

  • Save emailer.py.

Before you run the script, what do you expect to see in the terminal? Will it print your raw resume text again, or something different?

  • Run the script by running this command:
python emailer.py

You should see a character count first, then a professional cover email generated by Claude between the --- Generated Email --- and --- End of Email --- separators. The email should reference specific skills and experience from your resume and read like a natural cover letter.

Seeing an authentication error?

  • Make sure your ANTHROPIC_API_KEY environment variable is set in the same terminal session where you run the script. If you opened a new terminal since Step 1, you need to set it again.
  • Double-check that your API key is valid and has not been revoked in the Anthropic Console at platform.claude.com.
  • If you see a rate limit or spend limit error, your free credits may have been exhausted. Check your usage at platform.claude.com under Settings > Billing.
  • Why is Claude not generating my email?

✔️ Awesome, I've got everything!

Great. Double-check you've saved emailer.py.

ⓧ I'd like to double check the full code

Your emailer.py should now have five sections in this order. Scroll through your file and confirm each one is present:

  • Imports at the top (os, sys, smtplib, email modules, anthropic, PdfReader).
  • extract_resume_text() function from Step 3.
  • generate_email_body() function you just added.
  • send_email() function from Step 2.
  • if __name__ == "__main__": block at the bottom, calling extract_resume_text and then generate_email_body.

Your script can now read a resume and generate a professional cover email with AI. Next up, you'll wire everything together so the AI-generated email gets sent automatically with your resume attached.

Wire It All Together

You have three working pieces sitting in emailer.py. The PDF reader extracts text, Claude generates a professional email, and the email sender fires it off with an attachment. The problem is they are not truly connected.

Right now, your script has hardcoded values and prints results without acting on them. In this step, you will wire the three functions into a single flow so the script asks for inputs, extracts your resume, generates an AI email, and sends it in one clean run.

In this step, get ready to:
  • Build a main() function that connects all three pieces into a single pipeline.
  • Add interactive prompts so the script asks for email addresses and the resume path when it runs.
  • Run the complete script and receive a real AI-generated email with your resume attached.
Replace the script's main block with a main() function

Your current if __name__ == "__main__": block calls functions directly with hardcoded values. Wrapping the logic in a proper main() function lets you add interactive prompts, validation, and a clean sequence of operations.

  • In emailer.py, scroll down to the if __name__ == "__main__": block at the bottom of the file.
  • Find this block:
if __name__ == "__main__":
    resume_text = extract_resume_text("resume.pdf")
    print(f"Extracted {len(resume_text)} characters from resume.\n")
    email_body = generate_email_body(resume_text)
    print("\n--- Generated Email ---")
    print(email_body)
    print("--- End of Email ---")
  • Delete everything from that if __name__ line to the end of the file.
  • Paste the following code in its place:
def main():
    # Prompt the user for email details and resume path
    sender_email = input("Your Gmail address: ").strip()
    recipient_email = input("Recipient email address: ").strip()
    pdf_path = input("Path to your resume PDF: ").strip()

    # Stop the script if the resume file doesn't exist
    if not os.path.exists(pdf_path):
        print(f"Error: File not found: {pdf_path}")
        sys.exit(1)

    # Extract text content from the resume PDF
    print("\nExtracting resume text...")
    resume_text = extract_resume_text(pdf_path)
    print(f"Extracted {len(resume_text)} characters from resume.\n")

    # Send the resume text to Claude to generate a cover email
    print("Generating cover email with Claude AI...")
    email_body = generate_email_body(resume_text)
    print("\n--- Generated Email ---")
    print(email_body)
    print("--- End of Email ---\n")

    # Send the email with the AI-generated body and resume attached
    print("Sending email with resume attached...")
    send_email(sender_email, recipient_email, "Job Application", email_body, pdf_path)


if __name__ == "__main__":
    main()

What does this code do?

The main() function runs the three pieces you built in order, with interactive prompts and progress messages between each stage.

  • input() prompts ask for the sender Gmail, recipient email, and resume file path. .strip() removes accidental whitespace from each entry.
  • os.path.exists(pdf_path) checks the file is real before the script goes any further. If not, sys.exit(1) stops the script with an error code.
  • The three function calls happen in sequence: extract text, generate email body with Claude, then send the email with subject "Job Application" and the resume attached.
  • The generated email is printed to the terminal so you can review it before it lands in the recipient's inbox.
  • Save emailer.py.

✔️ Awesome, I've got everything!

Great. Make sure you have saved emailer.py before continuing.

ⓧ I'd like to double check the full code

Your emailer.py file now has four main sections. Scroll through and confirm each one is present in this order:

  • Imports at the top (os, sys, smtplib, the email modules, anthropic, and PdfReader).
  • The extract_resume_text() function (from Step 3).
  • The generate_email_body() function (from Step 4).
  • The send_email() function (from Step 2).
  • The new main() function you just pasted, followed by if __name__ == "__main__": main().
Run the complete script end-to-end

Everything is connected. Time to run the full pipeline and see a real AI-generated email land in an inbox.

Before you run this, think about what you expect to happen. The script will prompt you for three inputs, then run through extract, generate, and send. Do you think the email will arrive with a personalized body, or will something go wrong?

  • Run your script by entering this command in the terminal:
python emailer.py

What will the script ask you?

The script prompts for three inputs, one at a time. Enter each value and press Enter after each one.

  • Your Gmail address: the Gmail account you set up the App Password for.
  • Recipient email address: use your own email address for testing so you can check the result.
  • Path to your resume PDF: the file path to a PDF resume on your computer (e.g. resume.pdf if the PDF is in the same folder as emailer.py).

After you enter all three values, the script runs through each stage and prints progress to the terminal. You will see the extracted character count, then the full AI-generated email body printed between --- Generated Email --- markers, and finally an Email sent to confirmation.

  • Open the inbox for the recipient email you entered.
  • You should see a new email with the subject Job Application, a professional AI-generated body that mentions specific skills and experience from your resume, and your resume PDF attached.

Email didn't arrive?

  • Check your spam or junk folder. Gmail SMTP emails sometimes land there on the first send.
  • If you see Error: File not found in the terminal, double check that the path to your resume PDF is correct. If the PDF is not in the same folder as emailer.py, use the full file path.
  • If you see an authentication error, make sure your GMAIL_APP_PASSWORD environment variable is still set in the same terminal session you are running the script from.

Still stuck? Help me debug why my emailer.py script isn't sending the email successfully.

Secret mission

Tailor the Email to a Specific Job

Your emailer sends a solid generic cover email, but real job applications should speak directly to the role. Upgrade your script to accept a job description so Claude matches your resume to the specific company and requirements.

Clean Up Your Resources

Clean Up Your Resources

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

This project runs entirely locally, so there are no ongoing cloud costs. The Anthropic API only charges when you actively run the script, not for having a key.

Resources you used:

  • Gmail App Password (free, does not expire unless you revoke it).
  • Anthropic API key (pay-per-use after free credits are exhausted).
  • emailer.py file on your computer.
  • anthropic and pypdf Python packages.

Keep everything running

No action needed. Choose this if you plan to keep using the script for real job applications.

  • Your API key and App Password remain active. Nothing runs or costs money until you execute the script yourself.
  • Each time you run the script, Claude Haiku 4.5 costs a fraction of a cent per email generated.
  • Your emailer.py script is ready to use whenever you need to send a job application.

Pause - I'll come back to this later

Close your terminal session to clear the environment variables you set earlier. Your credentials and files stay in place for when you return.

  • Close any terminal windows where you set ANTHROPIC_API_KEY and GMAIL_APP_PASSWORD. This clears the variables from memory.
  • Your API key and App Password still exist in their respective dashboards. When you come back, re-export the environment variables in a new terminal and run the script again.

Delete - I don't want to use this again

Remove all project resources and revoke your credentials. This is permanent.

  • Revoke your Gmail App Password by going to myaccount.google.com/apppasswords. Find the app password you created and click the trash icon to remove it.
  • Delete your Anthropic API key by going to platform.claude.com. Open Settings, then API Keys. Click the menu icon next to your key and select Delete.
  • Delete your project file by running this command in the folder where you created emailer.py:

macOS

rm emailer.py

Windows

del emailer.py
  • Uninstall the Python packages by running:
pip uninstall anthropic pypdf -y

That's a Wrap!

That's a Wrap!

Nice work! You just built a fully functional AI-powered resume emailer from scratch using Python.

You've learned how to:

  • Extracted text from a PDF resume using pypdf and fed it into an AI model as input.
  • Connected to the Claude API using the anthropic Python SDK and generated professional email content with prompt engineering.
  • Sent real emails with PDF attachments through Gmail's SMTP server using Python's built-in smtplib and email modules, and wired everything into a reusable command-line tool.
  • Secret Mission: Upgraded the prompt to accept a job description and generate tailored, role-specific cover emails.

Ready to quiz yourself?