304 lines
9.1 KiB
Python
304 lines
9.1 KiB
Python
"""
|
|
Emailer Microservice Client
|
|
|
|
This module provides integration with the Emailer microservice,
|
|
a Rust-based REST API for sending emails via Gmail API.
|
|
|
|
Production URL: https://email.example.com
|
|
"""
|
|
import requests
|
|
from typing import List, Dict, Optional
|
|
from django.conf import settings
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class EmailerServiceError(Exception):
|
|
"""Base exception for emailer service errors"""
|
|
pass
|
|
|
|
|
|
class EmailerClient:
|
|
"""
|
|
Client for the Emailer microservice.
|
|
|
|
Features:
|
|
- Template-based emails with variable substitution
|
|
- Plain text and HTML email support
|
|
- Attachment support
|
|
- User impersonation for domain-wide delegation
|
|
- Health checking
|
|
|
|
Example:
|
|
emailer = EmailerClient()
|
|
emailer.send_template_email(
|
|
to=['user@example.com'],
|
|
template_id='notification',
|
|
variables={
|
|
'subject': 'Project Completed',
|
|
'team_member': 'John Doe',
|
|
'message': 'The project has been marked as completed.',
|
|
},
|
|
impersonate_user='noreply@example.com'
|
|
)
|
|
"""
|
|
|
|
def __init__(self, base_url: Optional[str] = None, api_key: Optional[str] = None):
|
|
"""
|
|
Initialize the emailer client.
|
|
|
|
Args:
|
|
base_url: Base URL of the emailer service. Defaults to settings.EMAILER_BASE_URL
|
|
api_key: API key for authentication. Defaults to settings.EMAILER_API_KEY
|
|
"""
|
|
self.base_url = base_url or getattr(
|
|
settings, 'EMAILER_BASE_URL', 'https://email.example.com'
|
|
)
|
|
self.api_key = api_key or getattr(settings, 'EMAILER_API_KEY', '')
|
|
self.timeout = 30 # seconds
|
|
|
|
if not self.api_key:
|
|
logger.warning("EMAILER_API_KEY not configured. Email sending will fail.")
|
|
|
|
def _get_headers(self, impersonate_user: Optional[str] = None) -> Dict[str, str]:
|
|
"""
|
|
Build request headers with authentication and optional impersonation.
|
|
|
|
Args:
|
|
impersonate_user: Email address to send from (requires domain-wide delegation)
|
|
|
|
Returns:
|
|
Dict of HTTP headers
|
|
"""
|
|
headers = {
|
|
'Content-Type': 'application/json',
|
|
'X-API-Key': self.api_key,
|
|
}
|
|
if impersonate_user:
|
|
headers['X-Impersonate-User'] = impersonate_user
|
|
return headers
|
|
|
|
def _handle_response(self, response: requests.Response) -> Dict:
|
|
"""
|
|
Handle API response and raise appropriate exceptions.
|
|
|
|
Args:
|
|
response: requests Response object
|
|
|
|
Returns:
|
|
Parsed JSON response
|
|
|
|
Raises:
|
|
EmailerServiceError: If the request failed
|
|
"""
|
|
try:
|
|
response.raise_for_status()
|
|
return response.json() if response.content else {}
|
|
except requests.exceptions.HTTPError as e:
|
|
error_detail = "Unknown error"
|
|
try:
|
|
error_data = response.json()
|
|
error_detail = error_data.get('message', error_data.get('error', str(e)))
|
|
except:
|
|
error_detail = response.text or str(e)
|
|
|
|
logger.error(
|
|
f"Emailer API error: {response.status_code} - {error_detail}",
|
|
extra={
|
|
'status_code': response.status_code,
|
|
'url': response.url,
|
|
'error': error_detail
|
|
}
|
|
)
|
|
raise EmailerServiceError(f"Email service error: {error_detail}")
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"Emailer request failed: {str(e)}")
|
|
raise EmailerServiceError(f"Failed to connect to email service: {str(e)}")
|
|
|
|
def send_email(
|
|
self,
|
|
to: List[str],
|
|
subject: str,
|
|
body: str,
|
|
cc: Optional[List[str]] = None,
|
|
bcc: Optional[List[str]] = None,
|
|
impersonate_user: Optional[str] = None,
|
|
) -> Dict:
|
|
"""
|
|
Send a plain email.
|
|
|
|
Args:
|
|
to: List of recipient email addresses
|
|
subject: Email subject
|
|
body: Email body (plain text)
|
|
cc: Optional CC recipients
|
|
bcc: Optional BCC recipients
|
|
impersonate_user: Email address to send from (requires domain-wide delegation)
|
|
|
|
Returns:
|
|
dict: Response with 'id', 'threadId', and 'labelIds'
|
|
|
|
Raises:
|
|
EmailerServiceError: If the request fails
|
|
"""
|
|
data = {
|
|
'to': to,
|
|
'subject': subject,
|
|
'body': body,
|
|
}
|
|
if cc:
|
|
data['cc'] = cc
|
|
if bcc:
|
|
data['bcc'] = bcc
|
|
|
|
try:
|
|
response = requests.post(
|
|
f"{self.base_url}/api/v1/emails",
|
|
headers=self._get_headers(impersonate_user),
|
|
json=data,
|
|
timeout=self.timeout
|
|
)
|
|
return self._handle_response(response)
|
|
except Exception as e:
|
|
logger.exception("Failed to send email")
|
|
raise
|
|
|
|
def send_template_email(
|
|
self,
|
|
to: List[str],
|
|
template_id: str,
|
|
variables: Dict[str, str],
|
|
cc: Optional[List[str]] = None,
|
|
bcc: Optional[List[str]] = None,
|
|
impersonate_user: Optional[str] = None,
|
|
) -> Dict:
|
|
"""
|
|
Send an email using a pre-defined template.
|
|
|
|
Available templates:
|
|
- 'notification': General notifications
|
|
Variables: subject, team_member, message
|
|
- 'service_scheduled': Service scheduling notifications
|
|
Variables: team_member, customer_name, service_date, service_address
|
|
- 'project_update': Project status updates
|
|
Variables: team_member, project_name, project_status, message
|
|
|
|
Args:
|
|
to: List of recipient email addresses
|
|
template_id: Template identifier
|
|
variables: Template variables (depends on template)
|
|
cc: Optional CC recipients
|
|
bcc: Optional BCC recipients
|
|
impersonate_user: Email address to send from
|
|
|
|
Returns:
|
|
dict: Response with 'id', 'threadId', and 'labelIds'
|
|
|
|
Raises:
|
|
EmailerServiceError: If the request fails
|
|
"""
|
|
data = {
|
|
'to': to,
|
|
'template_id': template_id,
|
|
'variables': variables,
|
|
}
|
|
if cc:
|
|
data['cc'] = cc
|
|
if bcc:
|
|
data['bcc'] = bcc
|
|
|
|
try:
|
|
response = requests.post(
|
|
f"{self.base_url}/api/v1/templates/send",
|
|
headers=self._get_headers(impersonate_user),
|
|
json=data,
|
|
timeout=self.timeout
|
|
)
|
|
result = self._handle_response(response)
|
|
logger.info(
|
|
f"Template email sent successfully",
|
|
extra={
|
|
'template_id': template_id,
|
|
'recipients': to,
|
|
'email_id': result.get('id')
|
|
}
|
|
)
|
|
return result
|
|
except Exception as e:
|
|
logger.exception(f"Failed to send template email: {template_id}")
|
|
raise
|
|
|
|
def list_templates(self) -> List[str]:
|
|
"""
|
|
Get list of available email templates.
|
|
|
|
Returns:
|
|
list: List of template IDs
|
|
|
|
Raises:
|
|
EmailerServiceError: If the request fails
|
|
"""
|
|
try:
|
|
response = requests.get(
|
|
f"{self.base_url}/api/v1/templates",
|
|
headers=self._get_headers(),
|
|
timeout=self.timeout
|
|
)
|
|
return self._handle_response(response)
|
|
except Exception as e:
|
|
logger.exception("Failed to list templates")
|
|
raise
|
|
|
|
def get_template(self, template_id: str) -> Dict:
|
|
"""
|
|
Get details of a specific email template.
|
|
|
|
Args:
|
|
template_id: Template identifier
|
|
|
|
Returns:
|
|
dict: Template details including variables
|
|
|
|
Raises:
|
|
EmailerServiceError: If the request fails
|
|
"""
|
|
try:
|
|
response = requests.get(
|
|
f"{self.base_url}/api/v1/templates/{template_id}",
|
|
headers=self._get_headers(),
|
|
timeout=self.timeout
|
|
)
|
|
return self._handle_response(response)
|
|
except Exception as e:
|
|
logger.exception(f"Failed to get template: {template_id}")
|
|
raise
|
|
|
|
def health_check(self) -> bool:
|
|
"""
|
|
Check if the emailer service is healthy.
|
|
|
|
Returns:
|
|
bool: True if service is healthy, False otherwise
|
|
"""
|
|
try:
|
|
response = requests.get(
|
|
f"{self.base_url}/health",
|
|
timeout=5
|
|
)
|
|
return response.status_code == 200
|
|
except Exception as e:
|
|
logger.warning(f"Emailer health check failed: {e}")
|
|
return False
|
|
|
|
|
|
# Convenience function for quick access
|
|
def get_emailer_client() -> EmailerClient:
|
|
"""
|
|
Get a configured emailer client instance.
|
|
|
|
Returns:
|
|
EmailerClient: Configured client instance
|
|
"""
|
|
return EmailerClient()
|