#!/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