""" 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': """

Master Assets Downloaded Successfully

Campaign: {{ campaign_name }} ({{ campaign_id }})

Campaign Number: {{ campaign_number }}

Assets Downloaded: {{ asset_count }}

Status Updated: A1 → A2


Processed Assets:


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': """

Localized Assets Uploaded Successfully

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': """

Upload Failed

Filename: {{ filename }}

Tracking ID: {{ tracking_id }}

Error: {{ error }}


Please investigate the error.

""" }, 'a1_to_a2_partial': { 'subject': "⚠️ Partial Download - Campaign {campaign_name}", 'html': """

Campaign Partially Processed

Campaign: {{ campaign_name }} ({{ campaign_id }})

Campaign Number: {{ campaign_number }}

Total Assets: {{ total_assets }}

Successful: {{ successful }}

Failed: {{ failed }}


{% if successful > 0 %}

✅ Successfully Processed ({{ successful }}):

{% endif %} {% if failed > 0 %}

❌ Failed Assets ({{ failed }}):

{% endif %}

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': """

Asset Uploaded Successfully (A2→A3)

File Details:

Original Filename (from Box):
{{ filename }}

Clean Filename (in DAM):
{{ clean_filename }}

DAM Asset ID: {{ asset_id }}

Tracking ID: {{ tracking_id }}


Processing Details:

Master Asset ID: {{ master_asset_name }}

Uploaded to DAM Folder: {{ upload_folder }}

Downloaded from Box Folder: {{ box_folder }}


What Was Done:


Status: Asset processing complete.

Note: Campaign status will be updated to A3 once all assets are uploaded.

""" }, 'b1_to_b2_complete': { 'subject': "✅ Global Master Assets Downloaded - Campaign {campaign_name}", 'html': """

Global Master Assets Downloaded Successfully (B1→B2)

Campaign: {{ campaign_name }} ({{ campaign_id }})

Campaign Number: {{ campaign_number }}

Assets Downloaded: {{ asset_count }}

Status Updated: B1 → B2

Campaign Type: Global Masters


Processed Assets:


All Global Master assets have been downloaded from DAM and uploaded to Box with tracking IDs.

Box Folder: 349261192115 (Global Masters)

Campaign status has been updated from B1 to B2.

""" }, 'b1_to_b2_partial': { 'subject': "⚠️ Partial Download - Global Campaign {campaign_name}", 'html': """

Global Campaign Partially Processed (B1→B2)

Campaign: {{ campaign_name }} ({{ campaign_id }})

Campaign Number: {{ campaign_number }}

Total Assets: {{ total_assets }}

Successful: {{ successful }}

Failed: {{ failed }}


{% if successful > 0 %}

✅ Successfully Processed ({{ successful }}):

{% endif %} {% if failed > 0 %}

❌ Failed Assets ({{ failed }}):

{% endif %}

Status NOT updated. Campaign remains at B1.

The script will retry failed assets on the next run (every 5 minutes).

""" } } 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