""" Notifier - Email and Webhook Notifications Handles SMTP emails (Mailgun) and outgoing webhooks Compatible with Python 3.6+ """ import requests import logging import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from jinja2 import Template logger = logging.getLogger('Notifier') class Notifier: def __init__(self, config): self.config = config self.enabled = config['notifications']['enabled'] # SMTP configuration (preferred method) smtp_config = config['notifications'].get('smtp', {}) self.smtp_server = smtp_config.get('server') self.smtp_port = smtp_config.get('port', 587) self.smtp_user = smtp_config.get('user') self.smtp_password = smtp_config.get('password') self.sender_email = smtp_config.get('sender_email') self.recipients = config['notifications']['recipients'] self.webhook_config = config.get('webhooks', {}) def send_email(self, template_name, recipients, data): """ Send email via SMTP (Mailgun) Args: template_name: Name of email template recipients: List of email addresses or single email data: Template data dict """ if not self.enabled: logger.info("Notifications disabled, skipping email") return if not self.smtp_server or not self.smtp_user: logger.warning("SMTP not configured, skipping email") return try: # Simple templates (full template system would load from YAML) templates = { 'a1_to_a2_complete': { 'subject': "✅ Master Assets Downloaded - Campaign {campaign_name}", 'html': """
Campaign: {{ campaign_name }} ({{ campaign_id }})
Campaign Number: {{ campaign_number }}
Assets Downloaded: {{ asset_count }}
Status Updated: A1 → A2
{{ asset.tracking_id }}
All assets have been downloaded from DAM and uploaded to Box with tracking IDs.
Campaign status has been updated from A1 to A2.
""" }, 'a2_to_a3_complete': { 'subject': "✅ Localized Assets Uploaded - Campaign {campaign_name}", 'html': """Campaign: {{ campaign_name }}
Campaign ID: {{ campaign_id }}
Assets Uploaded: {{ asset_count }}
Status Updated: A2 → A3
All localized assets have been uploaded to DAM.
""" }, 'upload_failed': { 'subject': "❌ Upload Failed - {filename}", 'html': """Filename: {{ filename }}
Tracking ID: {{ tracking_id }}
Error: {{ error }}
Please investigate the error.
""" }, 'a1_to_a2_partial': { 'subject': "⚠️ Partial Download - Campaign {campaign_name}", 'html': """Campaign: {{ campaign_name }} ({{ campaign_id }})
Campaign Number: {{ campaign_number }}
Total Assets: {{ total_assets }}
Successful: {{ successful }}
Failed: {{ failed }}
{{ asset.tracking_id }}
Status NOT updated. Campaign remains at A1.
The script will retry failed assets on the next run (every 5 minutes).
""" }, 'a2_to_a3_file_uploaded': { 'subject': "✅ Asset Uploaded to DAM - {clean_filename}", 'html': """Original Filename (from Box):
{{ filename }}
Clean Filename (in DAM):
{{ clean_filename }}
DAM Asset ID: {{ asset_id }}
Tracking ID: {{ tracking_id }}
Master Asset ID: {{ master_asset_name }}
Uploaded to DAM Folder: {{ upload_folder }}
Downloaded from Box Folder: {{ box_folder }}
Status: Asset processing complete.
Note: Campaign status will be updated to A3 once all assets are uploaded.
""" } } template_config = templates.get(template_name, { 'subject': 'Ferrero Automation Notification', 'html': '{}
'.format(data) }) # Render subject and body subject = template_config['subject'].format(**data) html_template = Template(template_config['html']) html_body = html_template.render(**data) # Prepare email msg = MIMEMultipart('alternative') msg['Subject'] = subject msg['From'] = self.sender_email msg['To'] = ', '.join(recipients) if isinstance(recipients, list) else recipients # Attach HTML body html_part = MIMEText(html_body, 'html') msg.attach(html_part) # Send via SMTP with smtplib.SMTP(self.smtp_server, self.smtp_port) as server: server.starttls() server.login(self.smtp_user, self.smtp_password) server.send_message(msg) logger.info("Email sent via SMTP: {} to {}".format(template_name, recipients)) except Exception as e: logger.error("Email error: {}".format(str(e))) def send_webhook(self, url, payload): """ Send outgoing webhook notification Args: url: Webhook URL payload: dict to send as JSON Returns: bool: Success status """ try: # Get webhook config if exists webhook_config = None for name, config in self.webhook_config.items(): if config.get('url') == url: webhook_config = config break if not webhook_config: webhook_config = {'timeout_seconds': 10, 'auth': {}} # Prepare headers headers = {'Content-Type': 'application/json'} # Add auth if configured auth_config = webhook_config.get('auth', {}) if auth_config.get('type') == 'bearer' and auth_config.get('token'): headers['Authorization'] = 'Bearer {}'.format(auth_config['token']) elif auth_config.get('type') == 'basic': # Could add basic auth here if needed pass # Send webhook response = requests.post( url, json=payload, headers=headers, timeout=webhook_config.get('timeout_seconds', 10) ) if response.status_code in [200, 201, 202]: logger.info("Webhook sent successfully: {}".format(url)) return True else: logger.warning("Webhook failed: HTTP {} - {}".format( response.status_code, response.text[:200] )) return False except Exception as e: logger.error("Webhook error: {}".format(str(e))) return False