""" Email Agent Module. Handles email-related tasks using Microsoft Graph API. """ import logging import json from typing import Dict, Any, List, Optional from ..workflow.agentBase import AgentBase logger = logging.getLogger(__name__) class AgentEmail(AgentBase): """Agent for handling email-related tasks.""" def __init__(self): """Initialize the email agent.""" super().__init__() self.name = "email" self.label = "Email Agent" self.description = "Handles email composition and sending using Microsoft Graph API" self.capabilities = [ "email_composition", "email_draft_creation", "email_template_generation" ] self.serviceBase = None def setDependencies(self, serviceBase=None): """Set external dependencies for the agent.""" self.serviceBase = serviceBase async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]: """ Process an email-related task. Args: task: Task object containing: - prompt: Instructions for the agent - inputDocuments: List of documents to process - outputSpecifications: List of required output documents - context: Additional context including workflow info Returns: Dictionary containing: - feedback: Text response explaining what was done - documents: List of created documents """ try: # Extract task information prompt = task.get("prompt", "") inputDocuments = task.get("inputDocuments", []) outputSpecs = task.get("outputSpecifications", []) # Check AI service if not self.service.base: return { "feedback": "The Email agent requires an AI service to function.", "documents": [] } # Check if Microsoft connector is available if not hasattr(self.service, 'msft'): return { "feedback": "Microsoft connector not available. Please ensure Microsoft integration is properly configured.", "documents": [] } # Get Microsoft token token_data = self.service.msft.getMsftToken() if not token_data: # Create authentication trigger document auth_doc = self._createFrontendAuthTriggerDocument() return { "feedback": "Microsoft authentication required. Please authenticate to continue.", "documents": [auth_doc] } # Extract document data from input documentContents, attachments = self._processInputDocuments(inputDocuments) # Generate email subject and body using AI emailTemplate = await self._generateEmailTemplate(prompt, documentContents) # Create HTML preview of the email htmlPreview = self._createHtmlPreview(emailTemplate) # Attempt to create a draft email using Microsoft Graph API draft_result = self.service.msft.createDraftEmail( emailTemplate["recipient"], emailTemplate["subject"], emailTemplate["htmlBody"], attachments ) # Prepare output documents documents = [] # Process output specifications for spec in outputSpecs: label = spec.get("label", "") description = spec.get("description", "") if label.endswith(".html"): # Create the HTML template file templateDoc = self.formatAgentDocumentOutput( label, emailTemplate["htmlBody"], # Use the actual HTML body, not the preview "text/html" ) documents.append(templateDoc) elif label.endswith(".json"): # Create JSON template if requested templateJson = json.dumps(emailTemplate, indent=2) templateDoc = self.formatAgentDocumentOutput( label, templateJson, "application/json" ) documents.append(templateDoc) else: # Default to preview for other cases previewDoc = self.formatAgentDocumentOutput( label, htmlPreview, "text/html" ) documents.append(previewDoc) # Prepare feedback message if draft_result: feedback = f"Email draft created successfully for {emailTemplate.get('recipient')}. The subject is: '{emailTemplate['subject']}'" if attachments: feedback += f" with {len(attachments)} attachment(s)" feedback += ". You can open and edit it in your Outlook draft folder." else: feedback = "Email template created but could not save as draft. HTML preview and template are available as documents." return { "feedback": feedback, "documents": documents } except Exception as e: logger.error(f"Error in email agent: {str(e)}") return { "feedback": f"Error processing email task: {str(e)}", "documents": [] } def _createFrontendAuthTriggerDocument(self) -> Dict[str, Any]: """Create a document that triggers Microsoft authentication in the frontend.""" return { "name": "microsoft_auth", "ext": "html", "mimeType": "text/html", "data": """

Microsoft Authentication Required

Please click the button below to authenticate with Microsoft:

""", "base64Encoded": False, "metadata": { "isText": True } } def _processInputDocuments(self, input_docs: List[Dict[str, Any]]) -> tuple: """ Process input documents to extract content and prepare attachments. Args: input_docs: List of input documents Returns: Tuple of (document content text, list of attachments) """ documentContents = [] attachments = [] for doc in input_docs: docName = doc.get("name", "unnamed") if doc.get("ext"): docName = f"{docName}.{doc.get('ext')}" # Add document name to contents documentContents.append(f"\n\n--- {docName} ---\n") # Process document data directly if doc.get("data"): # Add to attachments with proper metadata attachments.append({ "name": docName, "document": { "data": doc["data"], "mimeType": doc.get("mimeType", "application/octet-stream"), "base64Encoded": doc.get("base64Encoded", False) } }) documentContents.append(f"Document attached: {docName}") else: documentContents.append(f"Document referenced: {docName}") return "\n".join(documentContents), attachments async def _generateEmailTemplate(self, prompt: str, documentContents: str) -> Dict[str, Any]: """ Generate email template using AI. Args: prompt: The task prompt documentContents: Extracted document content Returns: Email template dictionary with recipient, subject, body """ emailPrompt = f""" Create an email based on the following request: REQUEST: {prompt} DOCUMENT CONTENTS: {documentContents[:2000]}... (truncated if longer) Generate an email template with: 1. A relevant recipient (use placeholder or derive from content if possible) 2. A concise but descriptive subject line 3. A professional HTML-formatted email body 4. Appropriate greeting and closing Format your response as JSON with these fields: - recipient: email address - subject: subject line - plainBody: plain text version - htmlBody: HTML formatted version Only return valid JSON. No preamble or explanations. """ try: response = await self.service.base.callAi([ {"role": "system", "content": "You are an email template specialist. Create professional emails. Respond with valid JSON only."}, {"role": "user", "content": emailPrompt} ]) # Extract JSON from response jsonStart = response.find('{') jsonEnd = response.rfind('}') + 1 if jsonStart >= 0 and jsonEnd > jsonStart: template = json.loads(response[jsonStart:jsonEnd]) return template else: # Fallback plan logger.warning(f"Not able creating email template, generating fallback plan") return { "recipient": "recipient@example.com", "subject": "Information Regarding Your Request", "plainBody": f"This email is regarding your request: {prompt}", "htmlBody": f"

This email is regarding your request: {prompt}

" } except Exception as e: logger.warning(f"Error generating email template: {str(e)}") return { "recipient": "recipient@example.com", "subject": "Information Regarding Your Request", "plainBody": f"This email is regarding your request: {prompt}", "htmlBody": f"

This email is regarding your request: {prompt}

" } def _createHtmlPreview(self, emailTemplate: Dict[str, Any]) -> str: """ Create an HTML preview of the email template. Args: emailTemplate: Email template dictionary Returns: HTML string for preview """ html = f""" Email Preview: {emailTemplate.get('subject', 'Email Template')}

Email Template Preview

To:
{emailTemplate.get('recipient', 'recipient@example.com')}
Subject:
{emailTemplate.get('subject', 'No Subject')}
""" return html def getAgentEmail() -> AgentEmail: """Factory function to create and return an EmailAgent instance.""" return AgentEmail()