Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| Email Assistant Agent Workforce for brett@brettapps.com | |
| Agents: | |
| 1. Triage Agent β read inbox, classify, prioritise | |
| 2. Draft Agent β compose replies, store in Obsidian drafts | |
| 3. Summary Agent β thread summaries, action items | |
| 4. Archive Agent β label, move, snooze via Obsidian filing system | |
| """ | |
| import json, os, re, datetime, subprocess, sys | |
| from pathlib import Path | |
| OBSIDIAN_VAULT = Path(os.environ.get("OBSIDIAN_VAULT", "/tmp/email_vault")) | |
| EMAIL_DIR = OBSIDIAN_VAULT / "Email" | |
| for d in [EMAIL_DIR / "Inbox", EMAIL_DIR / "Drafts", EMAIL_DIR / "Sent", | |
| EMAIL_DIR / "Archive", EMAIL_DIR / "Templates", EMAIL_DIR / "Action_Items"]: | |
| d.mkdir(parents=True, exist_ok=True) | |
| PRIORITY_RULES = [ | |
| (r"urgent|asap|immediately|critical", "P1-Critical"), | |
| (r"invoice|payment|overdue|deadline", "P2-High"), | |
| (r"meeting|call|schedule|calendar", "P2-High"), | |
| (r" Brettapps|fair dinkum|trifecta|ebook|publish", "P2-High"), | |
| (r"newsletter|unsubscribe|no.?reply", "P4-Low"), | |
| (r"promo|sale|discount|limited.?time", "P5-Spam"), | |
| ] | |
| def triage_email(subject: str, body: str, sender: str) -> dict: | |
| text = f"{subject} {body} {sender}".lower() | |
| priority = "P3-Normal" | |
| category = "General" | |
| for pattern, level in PRIORITY_RULES: | |
| if re.search(pattern, text, re.IGNORECASE): | |
| priority = level | |
| break | |
| if "hf." in text or "huggingface" in text or "space" in text: | |
| category = "HF Spaces" | |
| elif "invoice" in text or "payment" in text: | |
| category = "Finance" | |
| elif "meeting" in text or "call" in text: | |
| category = "Meetings" | |
| elif "trifecta" in text or "racing" in text: | |
| category = "Racing" | |
| elif "ebook" in text or "book" in text or "publish" in text: | |
| category = "Publishing" | |
| needs_reply = priority in ("P1-Critical", "P2-High") | |
| return { | |
| "priority": priority, | |
| "category": category, | |
| "needs_reply": needs_reply, | |
| "suggested_label": f"{category} / {priority}", | |
| } | |
| def summarise_thread(emails: list) -> str: | |
| lines = [f"# Thread Summary β {datetime.date.today().isoformat()}\n"] | |
| lines.append(f"**Total messages:** {len(emails)}\n") | |
| for i, e in enumerate(emails, 1): | |
| lines.append(f"{i}. **{e.get('subject', '(no subject)')}** β {e.get('sender', 'unknown')} ({e.get('date', '')[:10]})") | |
| lines.append("\n## Action Items\n") | |
| action_items = [] | |
| for e in emails: | |
| body = e.get("body", "") | |
| if re.search(r"please\s+(review|send|confirm|reply|action|update)", body, re.IGNORECASE): | |
| action_items.append(f"- [ ] {e.get('subject', 'Action required')} β from {e.get('sender', 'unknown')}") | |
| if not action_items: | |
| action_items = ["- [ ] Review thread for any outstanding requests"] | |
| lines.extend(action_items[:5]) | |
| return "\n".join(lines) | |
| def draft_reply(original: dict, tone: str = "professional") -> str: | |
| name = original.get("sender", "there").split("@")[0].replace(".", " ").title() | |
| templates = { | |
| "professional": f"""Hi {name}, | |
| Thank you for your email regarding "{original.get('subject', '')}". I've reviewed your message and will follow up shortly. | |
| Best regards, | |
| Brett Anthony Sjoberg""", | |
| "friendly": f"""Hi {name}, | |
| Thanks for reaching out! I appreciate you getting in touch about "{original.get('subject', '')}". | |
| I'll get back to you with more details soon. | |
| Cheers, | |
| Brett""", | |
| "brief": f"""{name} β re: {original.get('subject', '')} | |
| Acknowledged. Will follow up. | |
| Brett""", | |
| } | |
| return templates.get(tone, templates["professional"]) | |
| def archive_email(email: dict, folder: str = "Archive") -> str: | |
| ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") | |
| fname = f"{ts}_{email.get('id', 'email')}.md" | |
| target = EMAIL_DIR / folder / fname | |
| md = f"""# {email.get('subject', '(no subject)')} | |
| **From:** {email.get('sender', '')} | |
| **Date:** {email.get('date', '')} | |
| **Priority:** {email.get('triage', {}).get('priority', 'P3-Normal')} | |
| **Category:** {email.get('triage', {}).get('category', 'General')} | |
| **Needs Reply:** {email.get('triage', {}).get('needs_reply', False)} | |
| **Stored:** {ts} | |
| --- | |
| {email.get('body', '')} | |
| """ | |
| target.write_text(md, encoding="utf-8") | |
| return str(target) | |
| def build_obsidian_email_vault(): | |
| index = f"""# Email Vault | |
| ## Brettapps Email System | |
| **Account:** brett@brettapps.com | |
| **Vault Root:** {EMAIL_DIR} | |
| ## Folders | |
| | Folder | Purpose | | |
| |--------|---------| | |
| | [Inbox](./Inbox) | New emails awaiting triage | | |
| | [Drafts](./Drafts) | AI-generated reply drafts | | |
| | [Sent](./Sent) | Emails sent | | |
| | [Archive](./Archive) | Processed emails | | |
| | [Templates](./Templates) | Reply templates | | |
| | [Action_Items](./Action_Items) | Tasks extracted from emails | | |
| ## Quick Stats | |
| - Inbox: {len(list((EMAIL_DIR/'Inbox').glob('*.md')))} files | |
| - Drafts: {len(list((EMAIL_DIR/'Drafts').glob('*.md')))} files | |
| - Archive: {len(list((EMAIL_DIR/'Archive').glob('*.md')))} files | |
| """ | |
| (EMAIL_DIR / "README.md").write_text(index, encoding="utf-8") | |
| def run_email_workflow(emails: list) -> dict: | |
| """Process a batch of emails through the workforce.""" | |
| results = {"processed": 0, "triaged": [], "drafted": [], "archived": [], "actions": []} | |
| for email in emails: | |
| results["processed"] += 1 | |
| # Agent 1: Triage | |
| triage = triage_email(email.get("subject", ""), email.get("body", ""), email.get("sender", "")) | |
| email["triage"] = triage | |
| results["triaged"].append({"id": email.get("id"), "priority": triage["priority"], "category": triage["category"]}) | |
| # Agent 2: Draft (if needs reply) | |
| if triage["needs_reply"]: | |
| draft = draft_reply(email, tone="professional") | |
| ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") | |
| draft_fname = f"draft_{ts}_{email.get('id', 'reply')}.md" | |
| draft_path = EMAIL_DIR / "Drafts" / draft_fname | |
| draft_md = f"""# Draft Reply β {email.get('subject', '')} | |
| **To:** {email.get('sender', '')} | |
| **Subject:** Re: {email.get('subject', '')} | |
| **Priority:** {triage['priority']} | |
| **Status:** Pending review | |
| --- | |
| {draft} | |
| """ | |
| draft_path.write_text(draft_md, encoding="utf-8") | |
| results["drafted"].append(str(draft_path)) | |
| # Agent 3: Summarise | |
| summary = summarise_thread([email]) | |
| results["actions"].append({"id": email.get("id"), "summary": summary[:200]}) | |
| # Agent 4: Archive | |
| archive_path = archive_email(email, folder="Archive") | |
| results["archived"].append(archive_path) | |
| build_obsidian_email_vault() | |
| return results | |