AI Agents
Building an Automated Job Search Agent with Email Notifications
A hands-on build for a production-ready AI agent that searches for jobs daily and emails you the fresh listings — wired together with GPT web search, SMTP email, and a Python scheduler.
Most job hunts die in the tab graveyard: a dozen open boards, the same filters re-typed every morning, and the best postings buried before you ever scroll to them. This build hands that grind to an agent. It searches for roles, keeps only the recent ones, and drops a clean summary into your inbox on a schedule — so you spend your energy applying, not refreshing.
Architecture Overview
The agent uses a simple, modular design built around decorators. Each capability — search, email, scheduling — is its own decorator you can stack onto any class. Add @search_tool, @email_tool, or @schedule_tool above a class and it inherits that behavior, with nothing tangled together.
@search_tool
@email_tool
@schedule_tool
Prerequisites & Setup
Before writing any code, make sure you have:
- Python 3.8+ installed
- OpenAI API key with GPT-4 access
- Email account for sending notifications (Gmail recommended)
- App password for your email account
Install required libraries
pip install openai schedule smtplib email python-dotenv
1. Search Tool Decorator
The search tool decorator uses Python's @ syntax to attach job search capabilities to any class:
def search_tool(api_key):
"""Decorator that adds job search capabilities to any class"""
def decorator(cls):
cls._openai_client = OpenAI(api_key=api_key)
def search_jobs(self, job_title, location, num_results=10):
query = f"Find {num_results} recent job postings for '{job_title}' in {location}"
response = self._openai_client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": query}],
tools=[{"type": "web_search_preview"}],
max_tokens=1500
)
return response.choices[0].message.content
cls.search_jobs = search_jobs
return cls
return decorator2. Email Tool Decorator
The email tool decorator adds email notification capabilities:
def email_tool(email, password, smtp_server="smtp.gmail.com"):
"""Decorator that adds email capabilities to any class"""
def decorator(cls):
cls._email = email
cls._password = password
cls._smtp_server = smtp_server
def send_email(self, to_email, subject, message):
msg = MIMEText(message)
msg['From'] = self._email
msg['To'] = to_email
msg['Subject'] = subject
with smtplib.SMTP(self._smtp_server, 587) as server:
server.starttls()
server.login(self._email, self._password)
server.send_message(msg)
return True
cls.send_email = send_email
return cls
return decorator3. Schedule Tool Decorator
The schedule tool decorator adds daily scheduling functionality:
def schedule_tool(cls):
"""Decorator that adds scheduling capabilities to any class"""
def schedule_daily(self, time_str, method_name):
method = getattr(self, method_name)
schedule.every().day.at(time_str).do(method)
print(f"Scheduled {method_name} daily at {time_str}")
def run_scheduler(self):
try:
while True:
schedule.run_pending()
time.sleep(60)
except KeyboardInterrupt:
print("Scheduler stopped")
cls.schedule_daily = schedule_daily
cls.run_scheduler = run_scheduler
return cls4. Building the CLI Job Agent
Now we assemble the CLI job agent using the decorators. The agent asks for job details interactively, then searches and emails on demand or on a daily schedule:
# Apply all three decorators to give our agent capabilities
@search_tool(api_key="your-openai-api-key")
@email_tool(email="your@email.com", password="your-app-password")
@schedule_tool
class JobAgent:
def __init__(self):
print("Welcome to JobAgent CLI!")
print("=" * 40)
def get_job_details(self):
"""Interactive CLI to get job search details"""
print("\nLet's set up your job search:")
job_title = input("Enter job title (e.g., 'Data Scientist'): ").strip()
location = input("Enter location (e.g., 'New York, NY'): ").strip()
recipient_email = input("Enter your email for notifications: ").strip()
return job_title, location, recipient_email
def find_and_send_jobs(self, job_title, location, recipient_email):
"""Main method that finds jobs and emails them"""
print(f"\nSearching for '{job_title}' jobs in {location}...")
print("Please wait while I search...")
# Use the search capability (from @search_tool)
jobs = self.search_jobs(job_title, location)
# Format the email
email_subject = f"Daily Jobs: {job_title} in {location}"
email_body = f"""
Here are today's fresh job postings:
{jobs}
---
Sent by your JobAgent CLI
Time: {datetime.now().strftime('%Y-%m-%d %H:%M')}
""".strip()
# Use the email capability (from @email_tool)
success = self.send_email(recipient_email, email_subject, email_body)
if success:
print("✓ Job search completed successfully!")
print(f"✓ Email sent to {recipient_email}")
else:
print("✗ Something went wrong with the email")
def start_cli(self):
"""Start the interactive CLI"""
while True:
print("\n" + "=" * 40)
print("JobAgent CLI - What would you like to do?")
print("1. Search for jobs once")
print("2. Set up daily job search")
print("3. Exit")
choice = input("\nEnter your choice (1-3): ").strip()
if choice == "1":
job_title, location, recipient_email = self.get_job_details()
self.find_and_send_jobs(job_title, location, recipient_email)
elif choice == "2":
job_title, location, recipient_email = self.get_job_details()
schedule_time = input("Enter daily search time (HH:MM format, e.g., 09:00): ").strip()
# Create a wrapper function for scheduling
def scheduled_search():
self.find_and_send_jobs(job_title, location, recipient_email)
# Use the schedule capability (from @schedule_tool)
self.schedule_daily(schedule_time, scheduled_search)
print(f"✓ Daily job search scheduled for {schedule_time}")
print("✓ Running initial search...")
self.find_and_send_jobs(job_title, location, recipient_email)
print("\nStarting daily scheduler... Press Ctrl+C to stop")
self.run_scheduler()
elif choice == "3":
print("Thanks for using JobAgent CLI!")
break
else:
print("Invalid choice. Please enter 1, 2, or 3.")5. Running Your CLI Agent
Create a simple main file to start the interactive CLI:
# main.py - Start your CLI job agent
from agent import JobAgent
def main():
# Create and start your CLI job agent
agent = JobAgent()
agent.start_cli()
if __name__ == "__main__":
main()CLI output example
$ python main.py Welcome to JobAgent CLI! ======================================== ======================================== JobAgent CLI - What would you like to do? 1. Search for jobs once 2. Set up daily job search 3. Exit Enter your choice (1-3): 1 Let's set up your job search: Enter job title (e.g., 'Data Scientist'): Software Engineer Enter location (e.g., 'New York, NY'): San Francisco, CA Enter your email for notifications: john@email.com Searching for 'Software Engineer' jobs in San Francisco, CA... Please wait while I search... ✓ Job search completed successfully! ✓ Email sent to john@email.com
6. Setup and Installation
To set up and run the CLI job search agent:
# Install required packages pip install openai schedule # Update your API keys in the decorators (in your agent.py file) # Run your interactive CLI agent python main.py
Key Concepts Explained
Python decorators
- Use the @ symbol to apply functions to classes
- Add functionality without modifying original code
- Stack multiple decorators for combined features
- Reusable across different agent types
Modular design
- Each tool is independent and testable
- Easy to add new capabilities
- Clean separation of concerns
- Extensible for other automation tasks
Conclusion
You've built an AI agent using a clean, modular approach with decorators — one that actually solves a real problem instead of demoing a toy.
The decorator pattern here generalizes far beyond job hunting. Swap the search and email logic and the same skeleton powers price-monitoring agents for e-commerce, social media content agents, news summarization agents, stock market analysis agents, and plenty more.