diff --git a/email-summary-agent/.env.example b/email-summary-agent/.env.example new file mode 100644 index 0000000..be03b7a --- /dev/null +++ b/email-summary-agent/.env.example @@ -0,0 +1,17 @@ +# AgentMail Configuration +AGENTMAIL_API_KEY=your-agentmail-api-key +INBOX_USERNAME=summary-agent +WEBHOOK_DOMAIN=your-webhook-domain + +# AI Model Configuration +OPENAI_API_KEY=your-openai-api-key + +# Ngrok Configuration (if not using WEBHOOK_DOMAIN) +NGROK_AUTHTOKEN=your-ngrok-authtoken + +# Summary Configuration +SUMMARY_RECIPIENT_EMAIL=your-email@example.com +SUMMARY_TIME=17:00 + +# Optional: Send acknowledgment emails (true/false) +SEND_ACKNOWLEDGMENT=true diff --git a/email-summary-agent/.gitignore b/email-summary-agent/.gitignore new file mode 100644 index 0000000..b108af7 --- /dev/null +++ b/email-summary-agent/.gitignore @@ -0,0 +1,39 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +.venv/ +ENV/ +env.bak/ +venv.bak/ + +# Environment variables +.env +.env.local + +# Data files +email_data/ +*.json.bak + +# Logs +*.log + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Package managers +uv.lock +*.egg-info/ +dist/ +build/ diff --git a/email-summary-agent/README.md b/email-summary-agent/README.md new file mode 100644 index 0000000..a658036 --- /dev/null +++ b/email-summary-agent/README.md @@ -0,0 +1,584 @@ +# Email Summary Agent + +An AI-powered agent that collects all emails received throughout the day and sends you a comprehensive daily summary using the OpenAI API. + +This example is for demo purposes only and is not ready for production. + +## Features + +- **Automatic Email Collection**: Stores all emails received in your AgentMail inbox +- **AI-Powered Summaries**: Uses OpenAI API to generate intelligent, categorized summaries +- **Daily Schedule**: Sends summaries at a configurable time each day +- **Smart Categorization**: Groups emails by type (urgent, meetings, requests, etc.) +- **Action Items**: Highlights deadlines and tasks that need attention +- **Acknowledgment Messages**: Optionally sends auto-replies to senders +- **Test Data**: Includes 12 sample emails for testing + +## Use Cases + +- **Executive Digest**: Forward all work emails, get one AI-categorized summary at end of day +- **Newsletter Consolidation**: Route all newsletters to agent's inbox, receive one morning digest with key insights +- **Shared Inbox Monitoring**: Forward info@/hr@ emails, entire team receives morning summary with visibility +- **Sales Pipeline Tracking**: BCC agent on prospect emails, manager gets daily summary of deals and sentiment + + +## How It Works + +``` +1. Emails arrive at summary-agent@agentmail.to throughout the day +2. At scheduled time (default 5 PM), agent fetches all emails from inbox +3. AI analyzes all emails and generates a categorized summary +4. Summary email sent to your configured address +``` + +## Requirements + +- Python 3.11 or higher +- [AgentMail API key](https://agentmail.io) +- [OpenAI API key](https://platform.openai.com) +- [Ngrok account](https://ngrok.com) (for receiving webhooks) + +## Quick Start + +### 1. Get API Keys + +You'll need three API keys: + +**AgentMail API Key** +- Sign up at [agentmail.io](https://agentmail.io) +- Get your API key from the dashboard + +**OpenAI API Key** +- Go to [platform.openai.com](https://platform.openai.com) +- Create an API key +- Make sure you have credits available + +**Ngrok Auth Token** (for webhooks) +- Sign up for free at [ngrok.com](https://ngrok.com) +- Get your auth token from [dashboard.ngrok.com](https://dashboard.ngrok.com/get-started/your-authtoken) + +### 2. Install Dependencies + +```sh +cd email-summary-agent + +# Create virtual environment +uv venv +source .venv/bin/activate # On Windows: .venv\Scripts\activate + +# Install packages +uv pip install . +``` + +### 3. Configure Environment + +Set your environment variables: + +```sh +export AGENTMAIL_API_KEY=your-agentmail-api-key +export OPENAI_API_KEY=your-openai-api-key +export NGROK_AUTHTOKEN=your-ngrok-authtoken +export SUMMARY_RECIPIENT_EMAIL=your-email@example.com + +# Optional settings +export INBOX_USERNAME=summary-agent +export SUMMARY_TIME=17:00 +export SEND_ACKNOWLEDGMENT=true +``` + +Or create a `.env` file: + +```sh +AGENTMAIL_API_KEY=your-agentmail-api-key +OPENAI_API_KEY=your-openai-api-key +NGROK_AUTHTOKEN=your-ngrok-authtoken +SUMMARY_RECIPIENT_EMAIL=your-email@example.com +INBOX_USERNAME=summary-agent +SUMMARY_TIME=17:00 +SEND_ACKNOWLEDGMENT=true +``` + +Then load it: +```sh +export $(grep -v '^#' .env | xargs) +``` + +### 4. Run the Agent + +```sh +python main.py +``` + +The agent will automatically: +- ✅ Create the AgentMail inbox (or use existing) +- ✅ Set up ngrok tunnel +- ✅ Create webhook +- ✅ Start collecting emails + +You'll see: +``` +============================================================ +Email Summary Agent Ready! +============================================================ +Inbox: summary-agent@agentmail.to +Summary Recipient: your-email@example.com +Daily Summary Time: 17:00 +``` + +### 5. Test the Agent + +**Option A: Load Sample Emails (Recommended for Testing)** + +Open a **new terminal** and load 12 realistic sample emails: + +```sh +# Send sample emails to the inbox (requires API key) +curl -X POST http://localhost:8080/load-samples \ + -H "X-API-Key: $AGENTMAIL_API_KEY" + +# Check emails were sent (no authentication required) +curl http://localhost:8080/emails/today + +# Generate and send summary immediately (requires API key) +curl -X POST http://localhost:8080/summary/now \ + -H "X-API-Key: $AGENTMAIL_API_KEY" +``` + +**Check your email!** You should receive an AI-generated summary within 10-30 seconds showing: +- Urgent items (server maintenance) +- Meetings (budget review) +- Action items (security training, invoices) +- Requests (feature requests, PR reviews) + +**Option B: Send Real Emails** + +Send a test email to your agent's inbox: + +**Send to:** `summary-agent@agentmail.to` + +The agent will: +1. ✅ Receive the email in its AgentMail inbox +2. ✅ Send acknowledgment (if enabled) +3. ✅ Include it in the next scheduled summary (default 5 PM) + +**Trigger summary anytime:** +```sh +curl -X POST http://localhost:8080/summary/now \ + -H "X-API-Key: $AGENTMAIL_API_KEY" +``` + +## Sending Emails to the Agent + +Once the agent is running, send emails to: **`summary-agent@agentmail.to`** + +You can send from: +- Your personal email +- Forwarding rules from other inboxes +- BCC on emails you want summarized +- Newsletter subscriptions + +**Trigger immediate summary anytime:** +```sh +curl -X POST http://localhost:8080/summary/now \ + -H "X-API-Key: $AGENTMAIL_API_KEY" +``` + +## Example Summary Email + +Here's what the daily summary looks like: + +``` +From: summary-agent@agentmail.to +To: your-email@example.com +Subject: Daily Email Summary - 2025-11-08 + +DAILY EMAIL SUMMARY +Friday, November 08, 2025 + +Total Emails Received: 12 + +=== URGENT / ACTION REQUIRED === + +🚨 Server Maintenance Tonight (11 PM - 3 AM) + From: mike.johnson@vendor.com + Action: Notify team, update status page + Downtime: 30 minutes expected + +⚠️ Security Training Deadline: November 15 + From: security@company.com + Action: Complete training at training.company.com (45 min) + +=== MEETINGS & EVENTS === + +📅 Q4 Budget Review - Tomorrow 2 PM + From: sarah.chen@techcorp.com + Location: Conference Room B + Prep: Spending reports, projections, budget requests + +📅 New Employee Orientation - Next Monday + From: recruiting@company.com + Action: Introduce yourself to 3 new team members + +=== REQUESTS & FOLLOW-UPS === + +💼 Feature Request: Multi-language Support + From: alex.rivera@client.com + Action: Schedule call next week (Tue-Thu available) + Topics: Timeline, languages, pricing + +📝 Code Review: PR #247 - Authentication Bug Fix + From: noreply@github.com + Action: Review requested from @tech-leads + +📋 Contract Review Needed - NDA with NewClient Corp + From: jessica.thompson@legal.com + Issues: Confidentiality period, data handling, termination + Action: Review marked-up document, discuss before responding + +=== BUSINESS OPPORTUNITIES === + +🤝 Partnership Proposal from PartnerTech + From: david.park@partner.com + Opportunity: Integration with 50K+ users + Action: Consider intro call + +=== FYI / INFORMATIONAL === + +📊 October Marketing Campaign Results + From: lisa.wong@marketing.com + Highlights: 45% conversion increase, 67% social engagement growth + +💰 Stripe Invoice: $2,450.00 due Nov 15 + From: invoices@stripe.com + Auto-charge to card ending in 4242 + +✅ AWS Support Case #12345678 Resolved + From: support@aws.amazon.com + Issue: EC2 performance resolved via instance upgrade + +🎟️ Early Bird Tickets: Tech Summit 2026 + From: events@industry.org + Dates: March 15-17, San Francisco + Savings: $400 (ends Nov 30) + +=== ACTION ITEMS SUMMARY === + +Today/Tomorrow: +• Prepare for Q4 budget meeting (2 PM tomorrow) +• Notify team about tonight's server maintenance + +This Week: +• Schedule call with alex.rivera@client.com (multi-language feature) +• Review GitHub PR #247 +• Review legal contract with jessica.thompson + +By November 15: +• Complete security training +• Pay Stripe invoice ($2,450) + +Consider: +• Partnership call with PartnerTech +• Tech Summit 2026 early bird registration + +--- + +DETAILED EMAIL LIST + +1. [09:15 AM] From: sarah.chen@techcorp.com + Subject: Q4 Budget Review Meeting - Tomorrow 2 PM + +2. [10:30 AM] From: mike.johnson@vendor.com + Subject: URGENT: Server maintenance window tonight + +3. [11:00 AM] From: recruiting@company.com + Subject: New Employee Orientation - Next Monday + +4. [11:45 AM] From: alex.rivera@client.com + Subject: Feature Request: Multi-language Support + +5. [12:20 PM] From: noreply@github.com + Subject: [ProjectX] Pull Request #247: Fix authentication bug + +6. [01:00 PM] From: invoices@stripe.com + Subject: Invoice for November 2025 - $2,450.00 + +7. [02:15 PM] From: lisa.wong@marketing.com + Subject: Campaign Performance Report - October + +8. [02:45 PM] From: security@company.com + Subject: ACTION REQUIRED: Security Training Completion + +9. [03:30 PM] From: david.park@partner.com + Subject: Partnership Proposal - Integration Opportunity + +10. [04:00 PM] From: support@aws.amazon.com + Subject: Your Support Case #12345678 - Resolved + +11. [04:30 PM] From: events@industry.org + Subject: Early Bird Tickets: Tech Summit 2026 + +12. [04:45 PM] From: jessica.thompson@legal.com + Subject: Contract Review - NDA with NewClient Corp + +--- +This summary was generated automatically by Email Summary Agent. +Inbox: summary-agent@agentmail.to +``` + +## Configuration + +### Change Summary Time + +Update `.env`: + +```sh +SUMMARY_TIME=18:30 # 6:30 PM +``` + +Time format: HH:MM (24-hour) + +### Disable Acknowledgment Emails + +By default, the agent sends a brief acknowledgment to every sender: + +``` +Thank you for your email. + +Your message has been received and will be included in today's summary. + +This is an automated response from the Email Summary Agent. +``` + +To disable: + +```sh +SEND_ACKNOWLEDGMENT=false +``` + +### Change Summary Recipient + +Update `.env`: + +```sh +SUMMARY_RECIPIENT_EMAIL=team@company.com +``` + +## API Endpoints + +**Authentication:** POST endpoints require API key authentication via the `X-API-Key` header. The API key must match the `AGENTMAIL_API_KEY` configured for this agent (ensures only the agent owner can trigger operations). + +### Health Check + +``` +GET /health +``` + +No authentication required. + +Returns: + +```json +{ + "status": "healthy", + "service": "email-summary-agent", + "emails_today": 12 +} +``` + +### Trigger Summary Now + +``` +POST /summary/now +``` + +**Authentication:** Required - Include `X-API-Key` header with your AgentMail API key. + +Fetches all emails from the AgentMail inbox and sends a summary immediately. + +**Example:** +```sh +curl -X POST http://localhost:8080/summary/now \ + -H "X-API-Key: your-agentmail-api-key" +``` + +Returns: + +```json +{ + "status": "success" +} +``` + +### Get Today's Emails + +``` +GET /emails/today +``` + +No authentication required. + +Fetches and returns all emails received today from the AgentMail inbox. + +### Load Sample Emails + +``` +POST /load-samples +``` + +**Authentication:** Required - Include `X-API-Key` header with your AgentMail API key. + +Sends 12 realistic sample emails to the inbox via AgentMail API for testing purposes. + +**Example:** +```sh +curl -X POST http://localhost:8080/load-samples \ + -H "X-API-Key: your-agentmail-api-key" +``` + +Returns: + +```json +{ + "status": "success", + "emails_sent": 12, + "emails_failed": 0 +} +``` + +## Architecture + +``` +email-summary-agent/ +├── main.py # Webhook server and scheduler +├── summary_agent.py # AI summarization and inbox fetching +├── setup_inbox.py # Standalone inbox setup script +├── sample_emails.json # Sample test emails +├── requirements.txt # Dependencies +└── pyproject.toml # Package configuration +``` + +### How It Works + +**Email Reception (main.py:48-90)** +- Webhook receives incoming email notifications +- Optionally sends acknowledgment to sender +- Emails are stored in AgentMail inbox + +**Sample Email Loading (main.py:131-161)** +- `/load-samples` endpoint reads sample_emails.json +- Sends each sample email to the inbox via AgentMail API +- Useful for testing without needing external email clients + +**Daily Scheduler (main.py:164-194)** +- Background thread checks time every minute +- At configured time, triggers summary generation +- Calls summary agent to fetch and summarize emails + +**Email Fetching (summary_agent.py:21-56)** +- Fetches all emails from AgentMail inbox for current day +- Uses `inboxes.messages.list()` with date filter +- Retrieves full message content for each email + +**AI Summary Generation (summary_agent.py:58-95)** +- Formats emails for GPT-4 +- Prompts AI to categorize and summarize +- Highlights urgent items and action items +- Generates structured summary with categories + +## Customization + +### Modify Summary Format + +Edit `summary_agent.py:20-35` to change the AI prompt: + +```python +system_prompt = """Your custom instructions here""" +``` + +### Add Custom Categories + +Update the categorization prompt to include your categories: + +```python +def generate_category_breakdown(self, emails: List[Dict]) -> Dict: + system_prompt = """Categorize into: Sales, Support, Engineering, etc.""" +``` + +## Troubleshooting + +### Summary Not Sending + +1. Check scheduler is running (should see "Summary scheduler started" in logs) +2. Verify `SUMMARY_TIME` format is HH:MM +3. Check `SUMMARY_RECIPIENT_EMAIL` is set +4. Ensure there are emails to summarize + +### Emails Not Being Received + +1. Verify webhook is receiving email notifications (check logs) +2. Verify AgentMail webhook is configured correctly +3. Check emails are arriving in the AgentMail inbox +4. Test with `/emails/today` endpoint to see what's in the inbox + +### OpenAI Errors + +1. Verify `OPENAI_API_KEY` is set +2. Check you have API credits +3. Verify using a supported model (gpt-4o) +4. Check API usage limits + +## Security Considerations + +- **Email Content**: All email content is sent to OpenAI for summarization +- **Storage**: Emails stored in AgentMail inbox (managed by AgentMail) +- **API Authentication**: + - POST endpoints (`/summary/now`, `/load-samples`) require API key authentication via the `X-API-Key` header + - Validates that the provided API key has access to the summary-agent inbox using the AgentMail SDK + - Only users with access to this specific inbox can trigger operations +- **Webhook Endpoint**: `/webhooks` endpoint has no authentication (called by AgentMail service) +- **Data Retention**: Emails persist in AgentMail inbox according to AgentMail's retention policies + +For production use: +- Add webhook signature verification to validate requests from AgentMail +- Review AgentMail's data retention and privacy policies +- Consider email content sensitivity before sending to OpenAI +- Secure your AgentMail API key (never commit it to version control, use environment variables) +- Consider adding rate limiting to prevent API abuse +- Consider caching API key validation results to reduce SDK calls on authenticated endpoints + +## Advanced Features + +### Multiple Summary Recipients + +Modify `summary_agent.py` to support multiple recipients in the `send_summary()` method, or create multiple summary agent instances with different recipients. + +### Weekly Summaries + +Add a weekly summary function to `summary_agent.py`: + +```python +def fetch_weekly_emails(self) -> List[Dict]: + """Fetch all emails from the past 7 days.""" + week_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=7) + + response = self.agentmail.inboxes.messages.list( + inbox_id=self.inbox_id, + after=week_start.isoformat(), + limit=500 + ) + + # Process messages similar to fetch_todays_emails() + ... +``` + +### Slack Integration + +Send summaries to Slack instead of email: + +```python +import requests + +def send_to_slack(summary): + webhook_url = os.environ.get('SLACK_WEBHOOK_URL') + requests.post(webhook_url, json={"text": summary}) +``` diff --git a/email-summary-agent/main.py b/email-summary-agent/main.py new file mode 100644 index 0000000..1156299 --- /dev/null +++ b/email-summary-agent/main.py @@ -0,0 +1,394 @@ +""" +Email Summary Agent - Main application entry point. +Collects emails throughout the day and sends daily summaries. +""" + +import os +import sys +import threading +import time +from functools import wraps +from datetime import datetime, time as dt_time +from flask import Flask, request, jsonify +from pyngrok import ngrok +from agentmail import AgentMail +from summary_agent import SummaryAgent + + +# Configuration +AGENTMAIL_API_KEY = os.environ.get("AGENTMAIL_API_KEY") +OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") +NGROK_AUTHTOKEN = os.environ.get("NGROK_AUTHTOKEN") +INBOX_USERNAME = os.environ.get("INBOX_USERNAME", "summary-agent") +WEBHOOK_DOMAIN = os.environ.get("WEBHOOK_DOMAIN") +SUMMARY_RECIPIENT_EMAIL = os.environ.get("SUMMARY_RECIPIENT_EMAIL") +SUMMARY_TIME = os.environ.get("SUMMARY_TIME", "17:00") # Default 5 PM + +# Check required environment variables +if not AGENTMAIL_API_KEY: + print("Error: AGENTMAIL_API_KEY not set") + sys.exit(1) + +if not OPENAI_API_KEY: + print("Error: OPENAI_API_KEY not set") + sys.exit(1) + +if not SUMMARY_RECIPIENT_EMAIL: + print("Error: SUMMARY_RECIPIENT_EMAIL not set") + sys.exit(1) + +# Initialize components +agentmail = AgentMail(api_key=AGENTMAIL_API_KEY) +summary_agent = None # Will be initialized in setup_infrastructure() with inbox_id +inbox_id = None # Will be set in setup_infrastructure() + +# Flask app for webhook handling +app = Flask(__name__) + + +def require_api_key(f): + """Decorator to require API key authentication for endpoints. + + Validates that the provided API key has access to the summary-agent inbox. + This ensures only users with access to this specific inbox can trigger operations. + """ + @wraps(f) + def decorated_function(*args, **kwargs): + # Check for API key in X-API-Key header + api_key = request.headers.get('X-API-Key') + + if not api_key: + return jsonify({ + "status": "error", + "message": "Missing API key. Include X-API-Key header with your AgentMail API key." + }), 401 + + # Validate that the API key has access to the summary-agent inbox + try: + # Create AgentMail client with provided API key + test_client = AgentMail(api_key=api_key) + + # Try to access the summary-agent inbox + # This will fail if the key doesn't have access to this inbox + test_client.inboxes.messages.list( + inbox_id=inbox_id, + limit=1 + ) + + # If we get here, the API key is valid and has access to the inbox + return f(*args, **kwargs) + + except Exception as e: + return jsonify({ + "status": "error", + "message": "Invalid API key or no access to this inbox. Unauthorized." + }), 403 + + return decorated_function + + +@app.route('/webhooks', methods=['POST']) +def handle_webhook(): + """Handle incoming AgentMail webhooks.""" + try: + data = request.json + print(f"\nReceived webhook: {data.get('event_type')}") + + if data.get('event_type') == 'email.received': + email_data = data.get('data', {}) + + # Extract email information + from_info = email_data.get('from', {}) + from_email = from_info.get('email', 'unknown@example.com') + subject = email_data.get('subject', 'No Subject') + + print(f"From: {from_email}") + print(f"Subject: {subject}") + print("Email received - will be fetched from inbox during summary generation") + + # Send acknowledgment (optional) + # You can disable this if you don't want auto-replies + send_acknowledgment = os.environ.get("SEND_ACKNOWLEDGMENT", "true").lower() == "true" + if send_acknowledgment and inbox_id: + agentmail.inboxes.messages.send( + inbox_id=inbox_id, + to=from_email, + subject=f"Re: {subject}", + text=f"""Thank you for your email. + +Your message has been received and will be included in today's summary. + +This is an automated response from the Email Summary Agent. +""" + ) + print(f"Sent acknowledgment to {from_email}") + + return jsonify({"status": "success"}), 200 + + except Exception as e: + print(f"Error processing webhook: {e}") + import traceback + traceback.print_exc() + return jsonify({"status": "error", "message": str(e)}), 500 + + +@app.route('/health', methods=['GET']) +def health_check(): + """Health check endpoint.""" + try: + emails = summary_agent.fetch_todays_emails() if summary_agent else [] + email_count = len(emails) + except: + email_count = 0 + return jsonify({ + "status": "healthy", + "service": "email-summary-agent", + "emails_today": email_count + }), 200 + + +@app.route('/summary/now', methods=['POST']) +@require_api_key +def send_summary_now(): + """Manually trigger a summary (useful for testing).""" + try: + summary_agent.send_summary() + return jsonify({"status": "success"}), 200 + except Exception as e: + return jsonify({"status": "error", "message": str(e)}), 500 + + +@app.route('/emails/today', methods=['GET']) +def get_todays_emails(): + """Get all emails received today.""" + try: + emails = summary_agent.fetch_todays_emails() + return jsonify({ + "count": len(emails), + "emails": emails + }), 200 + except Exception as e: + return jsonify({"status": "error", "message": str(e)}), 500 + + +@app.route('/load-samples', methods=['POST']) +@require_api_key +def load_sample_emails(): + """Send sample emails to the inbox for testing.""" + try: + import json + + # Read sample emails + with open('sample_emails.json', 'r') as f: + sample_emails = json.load(f) + + sent_count = 0 + failed_count = 0 + + # Send each sample email to the inbox with retry logic + # Note: from field will show as the sending inbox, but subject/body contain the sample data + for email in sample_emails: + # Include sender info in the body since we can't spoof from address + full_body = f"From: {email.get('from_name', '')} <{email.get('from_email', '')}>\n\n{email.get('body', '')}" + + # Retry up to 3 times per email + for attempt in range(3): + try: + agentmail.inboxes.messages.send( + inbox_id=inbox_id, + to=f"{INBOX_USERNAME}@agentmail.to", + subject=email.get('subject', 'No Subject'), + text=full_body + ) + sent_count += 1 + break + except Exception as send_err: + if attempt < 2: + print(f"Retry sending email '{email.get('subject')}' (attempt {attempt + 1}/3): {send_err}") + time.sleep(1) + else: + print(f"Failed to send email '{email.get('subject')}' after 3 attempts: {send_err}") + failed_count += 1 + + print(f"✓ Sent {sent_count}/{len(sample_emails)} sample emails to inbox") + if failed_count > 0: + print(f"✗ Failed to send {failed_count} emails") + + return jsonify({ + "status": "success" if failed_count == 0 else "partial", + "emails_sent": sent_count, + "emails_failed": failed_count + }), 200 + except Exception as e: + print(f"✗ Error loading sample emails: {e}") + import traceback + traceback.print_exc() + return jsonify({"status": "error", "message": str(e)}), 500 + + +def parse_time(time_str: str) -> dt_time: + """Parse time string in HH:MM format.""" + try: + hour, minute = map(int, time_str.split(':')) + return dt_time(hour=hour, minute=minute) + except: + print(f"Invalid time format: {time_str}, using default 17:00") + return dt_time(hour=17, minute=0) + + +def summary_scheduler(): + """Background thread that sends daily summaries at scheduled time.""" + print(f"Summary scheduler started - will send daily at {SUMMARY_TIME}") + + target_time = parse_time(SUMMARY_TIME) + last_sent_date = None + + while True: + try: + now = datetime.now() + current_time = now.time() + current_date = now.date() + + # Check if it's time to send and we haven't sent today yet + if (current_time.hour == target_time.hour and + current_time.minute == target_time.minute and + last_sent_date != current_date): + + print(f"\nSending daily summary at {now}") + summary_agent.send_summary() + last_sent_date = current_date + print("Summary sent!") + + # Sleep for 60 seconds before checking again + time.sleep(60) + + except Exception as e: + print(f"Error in summary scheduler: {e}") + import traceback + traceback.print_exc() + time.sleep(60) + + +def setup_infrastructure(): + """Set up AgentMail inbox and webhook.""" + + global summary_agent, inbox_id + + print("\n" + "=" * 60) + print("Setting up Email Summary Agent") + print("=" * 60) + + # Create inbox if it doesn't exist + try: + inbox = agentmail.inboxes.create( + username=INBOX_USERNAME, + domain="agentmail.to", + display_name="Email Summary Agent" + ) + inbox_id = inbox.inbox_id + print(f"✓ Inbox created: {INBOX_USERNAME}@agentmail.to") + print(f" Inbox ID: {inbox_id}") + print(f" Type: {type(inbox)}") + print(f" Inbox object: {inbox}") + except Exception as e: + if "already exists" in str(e).lower() or "AlreadyExistsError" in str(e): + print(f"✓ Inbox already exists: {INBOX_USERNAME}@agentmail.to") + # Get the inbox_id from existing inbox + response = agentmail.inboxes.list() + print(f" Response type: {type(response)}") + print(f" Response: {response}") + + # Find the inbox that matches our username + for existing_inbox in response.inboxes: + print(f" Checking inbox: {existing_inbox}") + print(f" Inbox ID: {existing_inbox.inbox_id}") + # AgentMail uses email address as inbox_id + if existing_inbox.inbox_id == f"{INBOX_USERNAME}@agentmail.to": + inbox_id = existing_inbox.inbox_id + print(f" ✓ Found matching inbox") + print(f" Inbox ID: {inbox_id}") + break + else: + # If no match found, use the first inbox (fallback) + if response.inboxes: + inbox_id = response.inboxes[0].inbox_id + print(f" Using first inbox as fallback: {inbox_id}") + + if not inbox_id: + print(f"✗ Error: Could not find inbox_id for {INBOX_USERNAME}") + raise Exception("Could not retrieve inbox_id") + else: + print(f"✗ Error creating inbox: {e}") + raise + + # Initialize summary agent with inbox_id + summary_agent = SummaryAgent(agentmail, inbox_id, INBOX_USERNAME, SUMMARY_RECIPIENT_EMAIL) + print(f"✓ Summary agent initialized") + + # Set up ngrok tunnel if domain not provided + webhook_url = None + if WEBHOOK_DOMAIN: + webhook_url = f"https://{WEBHOOK_DOMAIN}/webhooks" + print(f"✓ Using provided webhook domain: {WEBHOOK_DOMAIN}") + elif NGROK_AUTHTOKEN: + ngrok.set_auth_token(NGROK_AUTHTOKEN) + tunnel = ngrok.connect(8080, bind_tls=True) + # Extract the public URL from the tunnel object + public_url = tunnel.public_url + webhook_url = f"{public_url}/webhooks" + print(f"✓ Ngrok tunnel created: {public_url}") + else: + print("✗ Error: Either WEBHOOK_DOMAIN or NGROK_AUTHTOKEN must be set") + sys.exit(1) + + # Create webhook + try: + webhook = agentmail.webhooks.create( + url=webhook_url, + event_types=["message.received"] + ) + print(f"✓ Webhook configured: {webhook_url}") + print(f" Webhook ID: {webhook.webhook_id}") + except Exception as e: + if "already exists" in str(e).lower(): + print(f"✓ Webhook already exists: {webhook_url}") + else: + print(f"✗ Error creating webhook: {e}") + raise + + print("\n" + "=" * 60) + print("Email Summary Agent Ready!") + print("=" * 60) + print(f"Inbox: {INBOX_USERNAME}@agentmail.to") + print(f"Summary Recipient: {SUMMARY_RECIPIENT_EMAIL}") + print(f"Daily Summary Time: {SUMMARY_TIME}") + print(f"Webhook: {webhook_url}") + print("\nHow it works:") + print(" 1. Receives emails in AgentMail inbox throughout the day") + print(" 2. At scheduled time, fetches all emails from inbox") + print(" 3. AI generates a summary and sends to your email") + print("\nEndpoints:") + print(" POST /summary/now - Send summary immediately") + print(" GET /emails/today - View today's emails from inbox") + print(" POST /load-samples - Send 12 sample emails to inbox") + print("\nTo test: curl -X POST http://localhost:8080/load-samples") + print("=" * 60 + "\n") + + +def main(): + """Main application entry point.""" + + # Set up infrastructure + setup_infrastructure() + + # Start summary scheduler in background thread + scheduler_thread = threading.Thread(target=summary_scheduler, daemon=True) + scheduler_thread.start() + + # Start Flask server + print("Starting webhook server on port 8080...") + app.run(host='0.0.0.0', port=8080, debug=False) + + +if __name__ == "__main__": + main() diff --git a/email-summary-agent/pyproject.toml b/email-summary-agent/pyproject.toml new file mode 100644 index 0000000..cb3833d --- /dev/null +++ b/email-summary-agent/pyproject.toml @@ -0,0 +1,16 @@ +[project] +name = "agentmail-email-summary-agent" +version = "0.1.0" +description = "AgentMail email summary agent - Daily AI-powered email summaries" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "agentmail>=0.0.19", + "flask>=3.1.0", + "pyngrok>=7.0.0", + "openai>=1.0.0", + "python-dotenv>=1.0.0", +] + +[tool.setuptools] +py-modules = ["main", "summary_agent", "email_storage", "setup_inbox"] diff --git a/email-summary-agent/requirements.txt b/email-summary-agent/requirements.txt new file mode 100644 index 0000000..9694aa1 --- /dev/null +++ b/email-summary-agent/requirements.txt @@ -0,0 +1,5 @@ +agentmail>=0.0.19 +flask>=3.1.0 +pyngrok>=7.0.0 +openai>=1.0.0 +python-dotenv>=1.0.0 diff --git a/email-summary-agent/sample_emails.json b/email-summary-agent/sample_emails.json new file mode 100644 index 0000000..abdeeca --- /dev/null +++ b/email-summary-agent/sample_emails.json @@ -0,0 +1,86 @@ +[ + { + "from_email": "sarah.chen@techcorp.com", + "from_name": "Sarah Chen", + "subject": "Q4 Budget Review Meeting - Tomorrow 2 PM", + "body": "Hi Team,\n\nJust a reminder that we have our Q4 budget review meeting scheduled for tomorrow at 2 PM in Conference Room B.\n\nPlease come prepared with:\n- Current spending reports\n- Projected expenses for next quarter\n- Any budget adjustment requests\n\nSee you there!\n\nBest,\nSarah", + "received_at": "2025-11-08T09:15:00" + }, + { + "from_email": "mike.johnson@vendor.com", + "from_name": "Mike Johnson", + "subject": "URGENT: Server maintenance window tonight", + "body": "Hi,\n\nThis is urgent - we need to perform emergency maintenance on the production servers tonight from 11 PM - 3 AM EST.\n\nExpected downtime: 30 minutes\nAffected services: API, Web Dashboard\n\nPlease notify your team and update your status page.\n\nLet me know if you need to reschedule.\n\nMike Johnson\nSenior DevOps Engineer", + "received_at": "2025-11-08T10:30:00" + }, + { + "from_email": "recruiting@company.com", + "from_name": "HR Team", + "subject": "New Employee Orientation - Next Monday", + "body": "Hello,\n\nWe're excited to welcome 3 new team members starting next Monday!\n\nOrientation Schedule:\n- 9:00 AM: Welcome & Company Overview\n- 10:30 AM: IT Setup\n- 12:00 PM: Team Lunch\n- 2:00 PM: Department Introductions\n\nPlease make time to introduce yourself to our new colleagues.\n\nBest,\nHR Team", + "received_at": "2025-11-08T11:00:00" + }, + { + "from_email": "alex.rivera@client.com", + "from_name": "Alex Rivera", + "subject": "Feature Request: Multi-language Support", + "body": "Hi,\n\nOur international users have been requesting multi-language support for the dashboard. This would be a huge win for user adoption in EMEA and APAC regions.\n\nCan we schedule a call to discuss:\n1. Implementation timeline\n2. Languages to support initially\n3. Pricing impact\n\nI'm available next week Tuesday-Thursday.\n\nThanks!\nAlex", + "received_at": "2025-11-08T11:45:00" + }, + { + "from_email": "noreply@github.com", + "from_name": "GitHub", + "subject": "[ProjectX] Pull Request #247: Fix authentication bug", + "body": "Pull Request #247 has been opened by john.doe\n\nFix authentication bug causing session timeouts\n\nChanges:\n- Updated session management logic\n- Added retry mechanism for token refresh\n- Fixed edge case in logout flow\n\n3 files changed, 127 insertions(+), 43 deletions(-)\n\nReview requested from: @tech-leads", + "received_at": "2025-11-08T12:20:00" + }, + { + "from_email": "invoices@stripe.com", + "from_name": "Stripe", + "subject": "Invoice for November 2025 - $2,450.00", + "body": "Invoice #INV-2025-11-001\nAmount Due: $2,450.00\nDue Date: November 15, 2025\n\nServices:\n- API Requests: $1,200.00\n- Premium Support: $800.00\n- Additional Storage: $450.00\n\nPayment will be automatically charged to your card ending in 4242.\n\nView invoice: https://stripe.com/invoices/inv_abc123", + "received_at": "2025-11-08T13:00:00" + }, + { + "from_email": "lisa.wong@marketing.com", + "from_name": "Lisa Wong", + "subject": "Campaign Performance Report - October", + "body": "Hi team,\n\nOctober campaign results are in!\n\nHighlights:\n✓ 45% increase in conversions\n✓ 28% lower cost per acquisition\n✓ Email open rate: 34% (industry avg: 21%)\n✓ Social media engagement up 67%\n\nTop performing channels:\n1. Email marketing\n2. LinkedIn ads\n3. Content marketing\n\nFull report attached. Let's discuss next week's strategy meeting.\n\nLisa", + "received_at": "2025-11-08T14:15:00" + }, + { + "from_email": "security@company.com", + "from_name": "Security Team", + "subject": "ACTION REQUIRED: Security Training Completion", + "body": "Dear Team Member,\n\nOur records show you haven't completed the mandatory security awareness training.\n\nDeadline: November 15, 2025\nTime required: 45 minutes\nAccess: training.company.com\n\nTopics covered:\n- Phishing prevention\n- Password security\n- Data handling policies\n- Incident reporting\n\nPlease complete by the deadline to maintain system access.\n\nSecurity Team", + "received_at": "2025-11-08T14:45:00" + }, + { + "from_email": "david.park@partner.com", + "from_name": "David Park", + "subject": "Partnership Proposal - Integration Opportunity", + "body": "Hi,\n\nI'm reaching out from PartnerTech regarding a potential integration between our platforms.\n\nWe have 50K+ users who frequently request your product's features. A native integration could benefit both our user bases.\n\nWould you be interested in exploring this? Happy to share:\n- User demographics\n- Technical specs\n- Revenue sharing models\n\nLet me know if you'd like to schedule an intro call.\n\nBest regards,\nDavid Park\nBD Manager, PartnerTech", + "received_at": "2025-11-08T15:30:00" + }, + { + "from_email": "support@aws.amazon.com", + "from_name": "AWS Support", + "subject": "Your Support Case #12345678 - Resolved", + "body": "Hello,\n\nYour AWS Support case regarding EC2 instance performance has been resolved.\n\nIssue: High CPU utilization on production instances\nResolution: Upgraded instance type from t3.medium to t3.large\nStatus: Monitoring for 48 hours\n\nCurrent metrics show normal performance levels. We'll continue monitoring and close the case if no issues arise.\n\nCase ID: 12345678\nSeverity: Medium\nResolved by: Senior Cloud Engineer\n\nAWS Support Team", + "received_at": "2025-11-08T16:00:00" + }, + { + "from_email": "events@industry.org", + "from_name": "Industry Conference", + "subject": "Early Bird Tickets: Tech Summit 2026", + "body": "Don't miss out on Tech Summit 2026!\n\nDates: March 15-17, 2026\nLocation: San Francisco, CA\n\nEarly bird pricing (ends Nov 30):\n- Individual: $899 (save $400)\n- Team of 5: $3,995 (save $2,000)\n\nFeatured speakers:\n- AI & Machine Learning track\n- Cloud Infrastructure\n- Cybersecurity\n- Product Management\n\n200+ sessions, 50+ workshops, networking events\n\nRegister: techsummit.com/register\nCode: EARLY2026", + "received_at": "2025-11-08T16:30:00" + }, + { + "from_email": "jessica.thompson@legal.com", + "from_name": "Jessica Thompson", + "subject": "Contract Review - NDA with NewClient Corp", + "body": "Hi,\n\nI've reviewed the NDA with NewClient Corp. A few items need attention:\n\n1. Confidentiality period: They want 5 years, suggest 3 years\n2. Data handling clause needs clarification\n3. Termination conditions are one-sided\n\nRecommendations:\n- Modify Section 4.2 (data retention)\n- Add mutual termination clause\n- Clarify IP ownership in Section 7\n\nI've marked up the document. Let's discuss before we respond.\n\nJessica Thompson\nSenior Legal Counsel", + "received_at": "2025-11-08T16:45:00" + } +] diff --git a/email-summary-agent/setup_inbox.py b/email-summary-agent/setup_inbox.py new file mode 100644 index 0000000..63b25a4 --- /dev/null +++ b/email-summary-agent/setup_inbox.py @@ -0,0 +1,37 @@ +""" +Standalone script to create AgentMail inbox using the SDK. +""" + +import os +import sys +from agentmail import AgentMail + +# Configuration +AGENTMAIL_API_KEY = os.environ.get("AGENTMAIL_API_KEY") +INBOX_USERNAME = os.environ.get("INBOX_USERNAME", "summary-agent") + +if not AGENTMAIL_API_KEY: + print("Error: AGENTMAIL_API_KEY not set") + sys.exit(1) + +# Initialize AgentMail +agentmail = AgentMail(api_key=AGENTMAIL_API_KEY) + +print(f"Creating inbox: {INBOX_USERNAME}@agentmail.to") + +try: + inbox = agentmail.inboxes.create( + username=INBOX_USERNAME, + domain="agentmail.to", + display_name="Email Summary Agent" + ) + print(f"✓ Success! Inbox created: {INBOX_USERNAME}@agentmail.to") + print(f" Inbox ID: {inbox.inbox_id}") + print(f" Display Name: {inbox.display_name}") + +except Exception as e: + if "already exists" in str(e).lower(): + print(f"✓ Inbox already exists: {INBOX_USERNAME}@agentmail.to") + else: + print(f"✗ Error: {e}") + sys.exit(1) diff --git a/email-summary-agent/summary_agent.py b/email-summary-agent/summary_agent.py new file mode 100644 index 0000000..b7f90f4 --- /dev/null +++ b/email-summary-agent/summary_agent.py @@ -0,0 +1,363 @@ +""" +Email summary agent that generates daily summaries using AI. +""" + +import os +import time +from datetime import date, datetime, timedelta +from typing import List, Dict, Optional +from openai import OpenAI, OpenAIError, RateLimitError, APITimeoutError + + +class SummaryAgent: + """AI agent that summarizes daily emails.""" + + def __init__(self, agentmail_client, inbox_id: str, inbox_username: str, summary_recipient_email: str): + self.client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) + self.agentmail = agentmail_client + self.inbox_id = inbox_id + self.inbox_username = inbox_username + self.summary_recipient_email = summary_recipient_email + + def fetch_todays_emails(self, max_retries: int = 3) -> List[Dict]: + """Fetch all emails from today from the AgentMail inbox with retry logic.""" + + # Get start of today in UTC (timezone-aware) + from datetime import timezone + today_start = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) + + for attempt in range(max_retries): + try: + # List messages from today - pass datetime object, not string + response = self.agentmail.inboxes.messages.list( + inbox_id=self.inbox_id, + after=today_start, + limit=100 # Adjust as needed + ) + + emails = [] + for message in response.messages: + # Fetch full message content with retry for individual messages + full_message = None + for msg_attempt in range(2): # Retry individual message fetches + try: + full_message = self.agentmail.inboxes.messages.get( + inbox_id=self.inbox_id, + message_id=message.message_id + ) + break + except Exception as msg_err: + if msg_attempt == 0: + print(f"Retrying message fetch for {message.message_id}: {msg_err}") + time.sleep(1) + else: + print(f"Failed to fetch message {message.message_id}: {msg_err}") + continue + + if not full_message: + continue + + # Convert datetime to ISO string for consistency + received_at = full_message.created_at + if hasattr(received_at, 'isoformat'): + received_at = received_at.isoformat() + + # Handle from_ field - could be string or object + from_email = 'unknown@example.com' + from_name = '' + if hasattr(full_message, 'from_'): + if isinstance(full_message.from_, str): + from_email = full_message.from_ + elif hasattr(full_message.from_, 'email'): + from_email = full_message.from_.email + from_name = full_message.from_.name if hasattr(full_message.from_, 'name') else '' + + emails.append({ + 'from_email': from_email, + 'from_name': from_name, + 'subject': full_message.subject or 'No Subject', + 'body': full_message.text or full_message.html or '', + 'received_at': received_at + }) + + print(f"Fetched {len(emails)} emails from AgentMail inbox") + return emails + + except Exception as e: + print(f"Error fetching emails from AgentMail (attempt {attempt + 1}/{max_retries}): {e}") + if attempt < max_retries - 1: + wait_time = 2 ** attempt # Exponential backoff: 1s, 2s, 4s + print(f"Retrying in {wait_time} seconds...") + time.sleep(wait_time) + else: + print("Max retries reached. Failed to fetch emails.") + import traceback + traceback.print_exc() + return [] + + return [] + + def generate_summary(self, emails: List[Dict], max_retries: int = 3) -> Optional[str]: + """Generate a summary of all emails using AI with retry logic.""" + + if not emails: + return "No emails received today." + + # Prepare email content for AI + email_content = self._format_emails_for_ai(emails) + + # Use OpenAI to generate summary + system_prompt = """You are an executive assistant that creates concise daily email summaries. + +Your summary should: +- Group emails by category (urgent, meetings, requests, FYI, etc.) +- Highlight action items and deadlines +- Note important senders +- Be concise but informative +- Use bullet points for easy scanning +- Flag anything that needs immediate attention + +Format the summary in a professional, easy-to-read style.""" + + user_prompt = f"""Please summarize these {len(emails)} emails received today: + +{email_content} + +Create a comprehensive daily email summary.""" + + for attempt in range(max_retries): + try: + response = self.client.chat.completions.create( + model="gpt-4o", + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt} + ], + temperature=0.7, + timeout=60.0 # 60 second timeout + ) + + return response.choices[0].message.content + + except RateLimitError as e: + print(f"OpenAI rate limit hit (attempt {attempt + 1}/{max_retries}): {e}") + if attempt < max_retries - 1: + wait_time = 10 * (2 ** attempt) # 10s, 20s, 40s + print(f"Waiting {wait_time} seconds before retry...") + time.sleep(wait_time) + else: + print("Max retries reached. Rate limit exceeded.") + return None + + except APITimeoutError as e: + print(f"OpenAI API timeout (attempt {attempt + 1}/{max_retries}): {e}") + if attempt < max_retries - 1: + wait_time = 5 * (2 ** attempt) # 5s, 10s, 20s + print(f"Retrying in {wait_time} seconds...") + time.sleep(wait_time) + else: + print("Max retries reached. API timeout.") + return None + + except OpenAIError as e: + print(f"OpenAI API error (attempt {attempt + 1}/{max_retries}): {e}") + if attempt < max_retries - 1: + wait_time = 3 * (2 ** attempt) # 3s, 6s, 12s + print(f"Retrying in {wait_time} seconds...") + time.sleep(wait_time) + else: + print("Max retries reached. OpenAI API error.") + import traceback + traceback.print_exc() + return None + + except Exception as e: + print(f"Unexpected error generating summary: {e}") + import traceback + traceback.print_exc() + return None + + return None + + def _format_emails_for_ai(self, emails: List[Dict]) -> str: + """Format emails into a readable format for AI processing.""" + + formatted = [] + for i, email in enumerate(emails, 1): + email_text = f""" +Email #{i} +From: {email.get('from_email', 'Unknown')} +Subject: {email.get('subject', 'No Subject')} +Time: {email.get('received_at', 'Unknown')} + +{email.get('body', 'No content')} + +--- +""" + formatted.append(email_text) + + return "\n".join(formatted) + + def create_summary_email(self, emails: List[Dict], summary_date: date) -> Optional[str]: + """Create a formatted summary email.""" + + # Generate AI summary + ai_summary = self.generate_summary(emails) + + if ai_summary is None: + # AI summary generation failed + return None + + # Create email header + date_str = summary_date.strftime("%A, %B %d, %Y") + + summary_email = f"""DAILY EMAIL SUMMARY +{date_str} + +Total Emails Received: {len(emails)} + +{ai_summary} + +--- + +DETAILED EMAIL LIST +""" + + # Add brief details of each email + for i, email in enumerate(emails, 1): + time_str = email.get('received_at', 'Unknown time') + if time_str != 'Unknown time': + try: + dt = datetime.fromisoformat(time_str) + time_str = dt.strftime("%I:%M %p") + except: + pass + + summary_email += f""" +{i}. [{time_str}] From: {email.get('from_email', 'Unknown')} + Subject: {email.get('subject', 'No Subject')} +""" + + summary_email += f""" + +--- +This summary was generated automatically by Email Summary Agent. +Inbox: {self.inbox_username}@agentmail.to +""" + + return summary_email + + def send_summary(self, summary_date: date = None): + """Fetch today's emails and send the daily summary.""" + + if summary_date is None: + summary_date = date.today() + + date_str = summary_date.strftime("%Y-%m-%d") + + try: + # Fetch emails from AgentMail inbox + emails = self.fetch_todays_emails() + + if not emails: + print("No emails to summarize today") + return + + # Create summary + summary_content = self.create_summary_email(emails, summary_date) + + if summary_content is None: + # AI summary generation failed - send error notification + print("AI summary generation failed. Sending error notification to user.") + self._send_error_notification( + date_str, + f"Failed to generate AI summary for {len(emails)} emails. Please check the application logs.", + len(emails) + ) + return + + # Send via AgentMail + subject = f"Daily Email Summary - {date_str}" + + self.agentmail.inboxes.messages.send( + inbox_id=self.inbox_id, + to=self.summary_recipient_email, + subject=subject, + text=summary_content + ) + + print(f"Sent summary of {len(emails)} emails to {self.summary_recipient_email}") + + except Exception as e: + print(f"Error sending summary: {e}") + import traceback + traceback.print_exc() + + # Try to send error notification + try: + self._send_error_notification( + date_str, + f"An error occurred while generating your daily email summary: {str(e)}" + ) + except Exception as notify_err: + print(f"Failed to send error notification: {notify_err}") + + def _send_error_notification(self, date_str: str, error_message: str, email_count: int = 0): + """Send an error notification to the user when summary generation fails.""" + try: + subject = f"⚠️ Email Summary Failed - {date_str}" + body = f"""EMAIL SUMMARY ERROR NOTIFICATION +{date_str} + +{error_message} + +{"Emails received: " + str(email_count) if email_count > 0 else ""} + +The Email Summary Agent encountered an error and could not generate your daily summary. +Please check the application logs for more details. + +--- +This is an automated error notification from Email Summary Agent. +Inbox: {self.inbox_username}@agentmail.to +""" + + self.agentmail.inboxes.messages.send( + inbox_id=self.inbox_id, + to=self.summary_recipient_email, + subject=subject, + text=body + ) + + print(f"Sent error notification to {self.summary_recipient_email}") + + except Exception as e: + print(f"Failed to send error notification: {e}") + + def generate_category_breakdown(self, emails: List[Dict]) -> Dict: + """Use AI to categorize emails.""" + + if not emails: + return {} + + email_list = "\n".join([ + f"{i}. From: {email.get('from_email')} - Subject: {email.get('subject')}" + for i, email in enumerate(emails, 1) + ]) + + system_prompt = """Categorize these emails into groups like: Urgent, Meetings, Requests, +FYI/Updates, Sales/Marketing, Personal, etc. Return as JSON with category names as keys +and lists of email numbers as values.""" + + user_prompt = f"""Categorize these emails:\n\n{email_list}""" + + response = self.client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt} + ], + response_format={"type": "json_object"} + ) + + import json + return json.loads(response.choices[0].message.content)