Skip to content

Commit b684b0a

Browse files
committed
Fix weekly job search workflow and add email notifications
- Add requirements_job_search.txt to root directory - Create directories before use to prevent errors - Handle missing files gracefully with continue-on-error - Add email notification functionality (send_job_search_email.py) - Email includes job summary, top matches, and attachments - Add EMAIL_SETUP.md with configuration instructions - Update workflow to use if-no-files-found: ignore for artifacts
1 parent 9ddfd98 commit b684b0a

File tree

4 files changed

+300
-18
lines changed

4 files changed

+300
-18
lines changed

.github/workflows/weekly_job_search.yml

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,16 @@ jobs:
2222
- name: Install dependencies
2323
run: |
2424
python -m pip install --upgrade pip
25-
pip install -r requirements_job_search.txt
26-
pip install beautifulsoup4 lxml python-docx
25+
if [ -f requirements_job_search.txt ]; then
26+
pip install -r requirements_job_search.txt
27+
else
28+
echo "⚠️ requirements_job_search.txt not found, installing default packages"
29+
pip install schedule requests python-docx beautifulsoup4 lxml
30+
fi
31+
32+
- name: Create directories
33+
run: |
34+
mkdir -p job_search_results job_search_reports resume_templates
2735
2836
- name: Create profile config (from template)
2937
run: |
@@ -47,39 +55,70 @@ jobs:
4755
USER_EMAIL: ${{ secrets.USER_EMAIL }}
4856

4957
- name: Run weekly job search
58+
continue-on-error: true
5059
run: |
51-
python run_neuro.py my_job_search.neuro
60+
python run_neuro.py my_job_search.neuro || echo "⚠️ Job search script exited with error, continuing..."
5261
5362
- name: Generate results report
63+
continue-on-error: true
5464
run: |
5565
# Get most recent results file
56-
LATEST_RESULT=$(ls -t job_search_results/job_search_*.json 2>/dev/null | head -1)
57-
if [ -n "$LATEST_RESULT" ]; then
58-
python display_results.py "$LATEST_RESULT"
66+
if [ -d job_search_results ]; then
67+
LATEST_RESULT=$(ls -t job_search_results/job_search_*.json 2>/dev/null | head -1)
68+
if [ -n "$LATEST_RESULT" ] && [ -f display_results.py ]; then
69+
python display_results.py "$LATEST_RESULT" || echo "⚠️ Could not generate report"
70+
else
71+
echo "⚠️ No results file or display_results.py found"
72+
fi
73+
else
74+
echo "⚠️ job_search_results directory not found"
5975
fi
6076
77+
- name: Send email notification
78+
continue-on-error: true
79+
run: |
80+
python send_job_search_email.py || echo "⚠️ Email sending failed (check credentials in secrets)"
81+
env:
82+
RECIPIENT_EMAIL: ${{ secrets.RECIPIENT_EMAIL }}
83+
SENDER_EMAIL: ${{ secrets.SENDER_EMAIL }}
84+
SENDER_PASSWORD: ${{ secrets.SENDER_PASSWORD }}
85+
6186
- name: Upload results as artifact
6287
uses: actions/upload-artifact@v4
6388
if: always()
6489
with:
6590
name: job-search-results-${{ github.run_number }}
6691
path: |
67-
job_search_results/
68-
job_search_reports/
69-
resume_templates/
92+
job_search_results/**
93+
job_search_reports/**
94+
resume_templates/**
95+
if-no-files-found: ignore
7096
retention-days: 30
7197

72-
- name: Comment on issue or create summary
98+
- name: Create summary
99+
if: always()
73100
run: |
74-
echo "## Weekly Job Search Results" >> $GITHUB_STEP_SUMMARY
75-
echo "" >> $GITHUB_STEP_SUMMARY
76-
echo "✅ Job search completed!" >> $GITHUB_STEP_SUMMARY
101+
echo "## 🔍 Weekly Job Search Results" >> $GITHUB_STEP_SUMMARY
77102
echo "" >> $GITHUB_STEP_SUMMARY
78-
echo "Results are available in the artifacts above." >> $GITHUB_STEP_SUMMARY
103+
echo "**Date:** $(date '+%Y-%m-%d %H:%M:%S')" >> $GITHUB_STEP_SUMMARY
79104
echo "" >> $GITHUB_STEP_SUMMARY
80-
echo "View results:" >> $GITHUB_STEP_SUMMARY
81-
LATEST_RESULT=$(ls -t job_search_results/job_search_*.json 2>/dev/null | head -1)
82-
if [ -n "$LATEST_RESULT" ]; then
83-
echo "- Latest results: \`$LATEST_RESULT\`" >> $GITHUB_STEP_SUMMARY
105+
106+
# Check if results exist
107+
if [ -d job_search_results ]; then
108+
RESULT_COUNT=$(find job_search_results -name "*.json" | wc -l)
109+
echo "✅ Found $RESULT_COUNT result file(s)" >> $GITHUB_STEP_SUMMARY
110+
111+
LATEST_RESULT=$(ls -t job_search_results/job_search_*.json 2>/dev/null | head -1)
112+
if [ -n "$LATEST_RESULT" ]; then
113+
echo "" >> $GITHUB_STEP_SUMMARY
114+
echo "📄 Latest results: \`$LATEST_RESULT\`" >> $GITHUB_STEP_SUMMARY
115+
fi
116+
else
117+
echo "⚠️ No results directory found" >> $GITHUB_STEP_SUMMARY
84118
fi
119+
120+
echo "" >> $GITHUB_STEP_SUMMARY
121+
echo "📧 Email notification sent (if configured)" >> $GITHUB_STEP_SUMMARY
122+
echo "" >> $GITHUB_STEP_SUMMARY
123+
echo "📦 Results are available in the artifacts above." >> $GITHUB_STEP_SUMMARY
85124

EMAIL_SETUP.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Email Setup for Weekly Job Search
2+
3+
The weekly job search workflow can now send results directly to your email. Here's how to set it up:
4+
5+
## Required GitHub Secrets
6+
7+
To enable email notifications, you need to add the following secrets to your GitHub repository:
8+
9+
1. Go to your repository on GitHub
10+
2. Navigate to **Settings****Secrets and variables****Actions**
11+
3. Click **New repository secret** and add:
12+
13+
### `SENDER_EMAIL`
14+
Your Gmail address (e.g., `yourname@gmail.com`)
15+
16+
### `SENDER_PASSWORD`
17+
Your Gmail App Password (NOT your regular password)
18+
19+
### `RECIPIENT_EMAIL` (Optional)
20+
The email address to receive notifications. If not set, defaults to `elena.mereanu@gmail.com`
21+
22+
## How to Get Gmail App Password
23+
24+
1. Go to your Google Account settings: https://myaccount.google.com/
25+
2. Navigate to **Security**
26+
3. Enable **2-Step Verification** if not already enabled
27+
4. Under **2-Step Verification**, click **App passwords**
28+
5. Select **Mail** and **Other (Custom name)** → enter "GitHub Actions"
29+
6. Click **Generate**
30+
7. Copy the 16-character password (this is your App Password)
31+
8. Use this password (not your regular Gmail password) as `SENDER_PASSWORD`
32+
33+
## Alternative: Custom SMTP Server
34+
35+
If you want to use a different email provider, you can modify `send_job_search_email.py`:
36+
37+
```python
38+
send_job_search_email(
39+
smtp_server="smtp.your-provider.com",
40+
smtp_port=587, # or 465 for SSL
41+
# ... other parameters
42+
)
43+
```
44+
45+
## Testing
46+
47+
After adding the secrets, you can:
48+
1. Manually trigger the workflow: **Actions****Weekly Job Search****Run workflow**
49+
2. Check the workflow logs to see if email was sent successfully
50+
3. If email fails, check that:
51+
- All secrets are correctly set
52+
- Gmail App Password is used (not regular password)
53+
- 2-Step Verification is enabled on Gmail
54+
55+
## Email Content
56+
57+
The email will include:
58+
- Summary of job search results
59+
- Top matching jobs with links
60+
- Attachments: Results JSON and report files (if available)
61+
62+
## Security Note
63+
64+
Never commit email credentials to the repository. Always use GitHub Secrets for sensitive information.
65+

requirements_job_search.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Requirements for Advanced Job Search System
2+
schedule>=1.2.0
3+
requests>=2.31.0
4+
python-docx>=1.0.0
5+
beautifulsoup4>=4.12.0
6+
lxml>=4.9.0
7+

send_job_search_email.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Send job search results via email
4+
"""
5+
import os
6+
import smtplib
7+
import json
8+
from email.mime.text import MIMEText
9+
from email.mime.multipart import MIMEMultipart
10+
from email.mime.base import MIMEBase
11+
from email import encoders
12+
from datetime import datetime
13+
from pathlib import Path
14+
15+
def send_job_search_email(
16+
results_file: str = None,
17+
report_file: str = None,
18+
recipient_email: str = None,
19+
sender_email: str = None,
20+
sender_password: str = None,
21+
smtp_server: str = "smtp.gmail.com",
22+
smtp_port: int = 587
23+
):
24+
"""
25+
Send job search results via email
26+
27+
Args:
28+
results_file: Path to JSON results file
29+
report_file: Path to text report file
30+
recipient_email: Email address to send to
31+
sender_email: Email address to send from (Gmail)
32+
sender_password: App password for Gmail
33+
smtp_server: SMTP server (default: Gmail)
34+
smtp_port: SMTP port (default: 587 for TLS)
35+
"""
36+
37+
# Get email from environment or use default
38+
recipient = recipient_email or os.getenv('RECIPIENT_EMAIL', 'elena.mereanu@gmail.com')
39+
sender = sender_email or os.getenv('SENDER_EMAIL')
40+
password = sender_password or os.getenv('SENDER_PASSWORD')
41+
42+
if not sender or not password:
43+
print("⚠️ Email credentials not configured. Skipping email send.")
44+
print(" Set SENDER_EMAIL and SENDER_PASSWORD environment variables or GitHub secrets")
45+
return False
46+
47+
# Create message
48+
msg = MIMEMultipart()
49+
msg['From'] = sender
50+
msg['To'] = recipient
51+
msg['Subject'] = f"🔍 Weekly Job Search Results - {datetime.now().strftime('%Y-%m-%d')}"
52+
53+
# Build email body
54+
body = f"""
55+
<html>
56+
<head></head>
57+
<body>
58+
<h2>🎯 Weekly Job Search Results</h2>
59+
<p><strong>Date:</strong> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
60+
61+
<h3>📊 Summary</h3>
62+
"""
63+
64+
# Add report content if available
65+
if report_file and os.path.exists(report_file):
66+
try:
67+
with open(report_file, 'r', encoding='utf-8') as f:
68+
report_content = f.read()
69+
body += f"<pre>{report_content}</pre>"
70+
except Exception as e:
71+
body += f"<p>⚠️ Could not read report file: {e}</p>"
72+
73+
# Add job count from results file
74+
if results_file and os.path.exists(results_file):
75+
try:
76+
with open(results_file, 'r', encoding='utf-8') as f:
77+
data = json.load(f)
78+
if isinstance(data, list):
79+
job_count = len(data)
80+
body += f"<p><strong>Total jobs found:</strong> {job_count}</p>"
81+
82+
# List top jobs
83+
if job_count > 0:
84+
body += "<h3>🏆 Top Matches</h3><ul>"
85+
for job in data[:10]: # Top 10
86+
title = job.get('title', 'N/A')
87+
company = job.get('company', 'N/A')
88+
url = job.get('url', '#')
89+
match_score = job.get('match_score', 0)
90+
body += f"<li><strong>{title}</strong> at {company} (Match: {match_score}%) - <a href='{url}'>View</a></li>"
91+
body += "</ul>"
92+
except Exception as e:
93+
body += f"<p>⚠️ Could not read results file: {e}</p>"
94+
95+
body += """
96+
<hr>
97+
<p><small>This email was automatically generated by Neuro Weekly Job Search.</small></p>
98+
</body>
99+
</html>
100+
"""
101+
102+
msg.attach(MIMEText(body, 'html'))
103+
104+
# Attach files if they exist
105+
attachments = []
106+
if results_file and os.path.exists(results_file):
107+
attachments.append(results_file)
108+
if report_file and os.path.exists(report_file):
109+
attachments.append(report_file)
110+
111+
for filepath in attachments:
112+
try:
113+
with open(filepath, 'rb') as f:
114+
part = MIMEBase('application', 'octet-stream')
115+
part.set_payload(f.read())
116+
encoders.encode_base64(part)
117+
part.add_header(
118+
'Content-Disposition',
119+
f'attachment; filename= {os.path.basename(filepath)}'
120+
)
121+
msg.attach(part)
122+
except Exception as e:
123+
print(f"⚠️ Could not attach {filepath}: {e}")
124+
125+
# Send email
126+
try:
127+
server = smtplib.SMTP(smtp_server, smtp_port)
128+
server.starttls()
129+
server.login(sender, password)
130+
text = msg.as_string()
131+
server.sendmail(sender, recipient, text)
132+
server.quit()
133+
print(f"✅ Email sent successfully to {recipient}")
134+
return True
135+
except Exception as e:
136+
print(f"❌ Error sending email: {e}")
137+
return False
138+
139+
if __name__ == "__main__":
140+
import sys
141+
142+
# Find latest results and report files
143+
results_dir = Path("job_search_results")
144+
reports_dir = Path("job_search_reports")
145+
146+
results_file = None
147+
report_file = None
148+
149+
# Find most recent results file
150+
if results_dir.exists():
151+
json_files = list(results_dir.glob("job_search_*.json"))
152+
if json_files:
153+
results_file = str(max(json_files, key=lambda p: p.stat().st_mtime))
154+
155+
# Find most recent report file
156+
if reports_dir.exists():
157+
report_files = list(reports_dir.glob("weekly_report_*.txt"))
158+
if report_files:
159+
report_file = str(max(report_files, key=lambda p: p.stat().st_mtime))
160+
161+
if not results_file and not report_file:
162+
print("⚠️ No results or report files found. Skipping email send.")
163+
sys.exit(0)
164+
165+
success = send_job_search_email(
166+
results_file=results_file,
167+
report_file=report_file
168+
)
169+
170+
sys.exit(0 if success else 1)
171+

0 commit comments

Comments
 (0)