| name | email-processing |
| description | Email, inbox, Outlook, Microsoft 365, mail, messages, fetch emails, search emails, draft, reply, email processing (project) |
| triggers | ["email","inbox","outlook","mail","messages","draft","reply"] |
| requires | {"scripts":["email_processor.py","microsoft_graph.py"],"config":["skills.entries.email-processing.enabled"]} |
| os | ["linux","darwin"] |
Email Processing
Outlook email integration via Microsoft Graph API. Fetch, search, and manage emails - including creating drafts (but NEVER sending automatically).
Quick Start
python microsoft_graph.py configure CLIENT_ID SECRET --tenant TENANT_ID
python microsoft_graph.py auth-url
python email_processor.py fetch
python email_processor.py search "budget report"
python email_processor.py draft --to "user@example.com" --subject "Subject" --body "Body text"
⚠️ CRITICAL: Draft-Only Policy
The email system creates DRAFTS only - it NEVER sends emails automatically.
When the user asks to "send an email" or "reply to X":
- Create a draft using
create_draft()
- Inform the user: "I've created a draft - please review and send from Outlook"
- Provide the web link if available
This is a deliberate safety feature. the user must manually send all emails.
Setup Requirements
Before using email features, Microsoft Graph must be configured:
1. Azure AD App Registration
- Go to https://portal.azure.com
- Azure AD → App registrations → New registration
- Name: "PCP Integration"
- Redirect URI: http://localhost:8080
- Note: client_id, tenant_id
- Certificates & secrets → New client secret
2. API Permissions
Required permissions:
Mail.Read - Read user's emails
Mail.ReadWrite - Create drafts
offline_access - Maintain access (refresh tokens)
3. Configure PCP
python microsoft_graph.py configure CLIENT_ID CLIENT_SECRET --tenant TENANT_ID
python microsoft_graph.py auth-url
python microsoft_graph.py authenticate CODE
python microsoft_graph.py status
Functions
Fetching Emails
from email_processor import fetch_new_emails
result = fetch_new_emails(limit=50)
if result['success']:
for email in result['emails']:
print(f"{email['sender']}: {email['subject']}")
Searching Emails
from email_processor import search_emails, get_email
results = search_emails("project update", days=7)
email = get_email(42)
print(email['body_full'])
Listing Emails
from email_processor import list_emails, get_actionable_emails
recent = list_emails(days=7, limit=50)
actionable = get_actionable_emails()
for email in actionable:
print(f"[!] {email['sender']}: {email['subject']}")
Creating Drafts (NEVER Sends)
from email_processor import create_draft
result = create_draft(
to="user@example.com, other@example.com",
subject="Re: Project Update",
body="Here's the update you requested...",
cc="manager@example.com"
)
if result['success']:
print(f"Draft created: {result['draft_id']}")
print(f"View in Outlook: {result['web_link']}")
print("NOTE: Draft must be sent manually from Outlook!")
CLI Commands
python email_processor.py fetch --limit 50
python email_processor.py search "query" --days 7
python email_processor.py list --days 7 --limit 50
python email_processor.py list --actionable
python email_processor.py get 42
python email_processor.py draft --to "user@example.com" --subject "Subject" --body "Body text" --cc "cc@example.com"
Actionability Detection
Emails are automatically flagged as "actionable" based on content:
| Category | Detected Phrases |
|---|
| Requests | please, could you, would you, need you to |
| Urgency | urgent, ASAP, by tomorrow, deadline |
| Questions | ?, what do you think, your thoughts |
| Responses | let me know, get back to me, waiting for |
| Meetings | schedule a, set up a meeting, calendar invite |
When User Says...
| User Says | Action |
|---|
| "Check my email" | python email_processor.py fetch && python email_processor.py list --actionable |
| "Any urgent emails?" | get_actionable_emails() |
| "Search for email about X" | search_emails("X") |
| "Show me that email from John" | search_emails("John") then get_email(ID) |
| "Reply to that email" / "Send email" | create_draft(...) + tell the user "Draft created, please send from Outlook" |
| "What did Sarah say about the budget?" | search_emails("Sarah budget") |
Integration with Briefs
Actionable emails appear in daily briefs:
from brief import daily_brief
brief = daily_brief()
Scheduled Sync
The scheduler can auto-fetch emails:
python scheduler.py --config
python scheduler.py --run email
Database Table
Emails are stored in the emails table:
| Column | Description |
|---|
| id | Database ID |
| message_id | Outlook message ID (unique) |
| subject | Email subject |
| sender | "Name " format |
| recipients | JSON array of recipient emails |
| body_preview | First 500 chars of body |
| body_full | Complete email content |
| is_actionable | Auto-detected actionability |
| action_taken | Notes on action taken |
| received_at | When email was received |
| processed_at | When PCP processed it |
Error Handling
result = fetch_new_emails()
if not result['success']:
error = result['error']
if 'not configured' in error.lower():
print("Run: python microsoft_graph.py configure ...")
elif 'not authenticated' in error.lower():
print("Run: python microsoft_graph.py auth-url")
else:
print(f"API Error: {error}")
Related Skills
- pcp-operations: System overview
- vault-operations: Captures and search
- brief-generation: Briefs include email sections
Remember: Draft-only policy. Never send emails automatically. Always inform the user that a draft was created and needs manual sending.