""" 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_number }})
Assets Downloaded: {{ asset_count }}
Status Updated: A1 → A2
Tracking ID: {{ asset.tracking_id }}
Box File ID: {{ asset.box_file_id }}
Box URL: {{ asset.box_url }}
{% if asset.folder_path %}DAM Path: {{ asset.folder_path }}
{% endif %}✓ Complete: All assets downloaded from DAM and uploaded to Box with tracking IDs.
Campaign status updated from A1 to A2. Box Folder: 348304357505 (Local Adaptation)
Campaign: {{ campaign_name }}
Campaign ID: {{ campaign_id }}
Assets Uploaded: {{ asset_count }}
Status Updated: A2 → A3
✓ Complete: All localized assets have been uploaded to DAM.
Campaign status updated from A2 to A3.
Filename: {{ filename }}
Tracking ID: {{ tracking_id }}
Error: {{ error }}
📌 Action Required: Please investigate the error and retry the upload.
Campaign: {{ campaign_name }} ({{ campaign_number }})
Total Assets: {{ total_assets }}
Successful: {{ successful }} | Failed: {{ failed }}
Tracking ID: {{ asset.tracking_id }}
Box File ID: {{ asset.box_file_id }}
Box URL: {{ asset.box_url }}
{% if asset.folder_path %}DAM Path: {{ asset.folder_path }}
{% endif %}Error: {{ asset.error }}
⚠️ Status NOT updated. Campaign remains at A1.
The script will retry failed assets on the next run (every 5 minutes).
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.
Campaign: {{ campaign_name }} ({{ campaign_number }})
Campaign Type: Global Masters
Assets Downloaded: {{ asset_count }}
Status Updated: B1 → B2
Tracking ID: {{ asset.tracking_id }}
Box File ID: {{ asset.box_file_id }}
Box URL: {{ asset.box_url }}
{% if asset.folder_path %}DAM Path: {{ asset.folder_path }}
{% endif %}✓ Complete: All Global Master assets downloaded from DAM and uploaded to Box with tracking IDs.
Campaign status updated from B1 to B2. Box Folder: 349261192115 (Global Masters)
Campaign: {{ campaign_name }} ({{ campaign_number }})
Campaign Type: Global Masters (B1→B2)
Total Assets: {{ total_assets }}
Successful: {{ successful }} | Failed: {{ failed }}
Tracking ID: {{ asset.tracking_id }}
Box URL: {{ asset.box_url }}
{% if asset.folder_path %}DAM Path: {{ asset.folder_path }}
{% endif %}Error: {{ asset.error }}
⚠️ Status NOT updated. Campaign remains at B1.
The script will retry failed assets on the next run (every 5 minutes).
⚠️ Campaign: {{ campaign_name }} ({{ campaign_number }})
NOT APPROVED assets: {{ rejected_count }} | Approved/skipped: {{ skipped_count }}
Status Updated: A5 → A6
Status: NOT APPROVED
Tracking ID: {{ asset.tracking_id }}{% if asset.is_existing %} (Updated existing){% endif %}
Box URL: {{ asset.box_url }}
{% if asset.folder_path %}DAM Path: {{ asset.folder_path }}
{% endif %}Reason: {{ approver.comment }}
{% if approver.certifier_name %}Rejected By: {{ approver.certifier_name }}
{% endif %} {% if approver.date %}Date: {{ approver.date }}
{% endif %}Reason: {{ legal.comment }}
{% if legal.certifier_name %}Rejected By: {{ legal.certifier_name }}
{% endif %} {% if legal.date %}Date: {{ legal.date }}
{% endif %}Reason: {{ ia_cc.comment }}
{% if ia_cc.certifier_name %}Rejected By: {{ ia_cc.certifier_name }}
{% endif %} {% if ia_cc.date %}Date: {{ ia_cc.date }}
{% endif %}All rejected assets have been downloaded to Box Revisions folder (349441822875). Campaign status updated A5 → A6.
Campaign: {{ campaign_name }} ({{ campaign_id }})
Campaign Number: {{ campaign_number }}
Total NOT APPROVED: {{ total_assets }}
Successful: {{ successful }}
Failed: {{ failed }}
{{ asset.tracking_id }}{% if asset.is_existing %} (Updated existing){% endif %}
Status NOT updated. Campaign remains at A5.
The script will retry failed assets on the next run (every 5 minutes).
""" }, 'a5_to_a6_no_rejections': { 'subject': "✅ No Rework Required - Campaign {campaign_name}", 'html': """Campaign: {{ campaign_name }} ({{ campaign_number }})
Total assets checked: {{ total_assets }}
Approved/other status: {{ skipped_count }}
NOT APPROVED assets: 0
All assets in the Final Assets folder are approved or have other status.
No assets with "NOT APPROVED" status were found, so no rework is required.
📌 Note:
A5→A6 script completed successfully with no rejected assets found.
Campaign: {{ campaign_name }} ({{ campaign_number }})
Campaign ID: {{ campaign_id }}
Status: A1
The Master Assets folder was searched (including subfolders) but no assets were found.
This campaign is set to status A1 but appears to have no master assets ready for download.
📌 What Happens Next:
A1→A2 script completed with no assets to process.
Campaign: {{ campaign_name }} ({{ campaign_number }})
Campaign ID: {{ campaign_id }}
Status: B1
Campaign Type: Global Masters
The Final Assets folder was searched (including subfolders) but no assets were found.
This Global Masters campaign is set to status B1 but appears to have no assets ready for download.
📌 What Happens Next:
B1→B2 script completed with no assets to process.
Campaign: {{ campaign_name }} ({{ campaign_number }})
Status: A4 (Not Going Live)
Live Campaign: NO
A webhook notification has been sent indicating this campaign is marked as A4 and will not go live.
Webhook URL: {{ webhook_url }}
Payload Sent:
A4 webhook monitor completed successfully.
{{ report_date }} - Generated at {{ report_time }}
No activity in the last 24 hours
{% endif %}📌 Automation Scripts: A1→A2, A2→A3, A5→A6, B1→B2
Scripts run every 5 minutes | Report generated daily at 7:00 PM
{}
'.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