Automating Email Notifications in Scripts
Table of Contents
1. [Introduction](#introduction) 2. [Email Protocols Overview](#email-protocols-overview) 3. [Setting Up Email Configuration](#setting-up-email-configuration) 4. [Python Implementation](#python-implementation) 5. [Bash Shell Implementation](#bash-shell-implementation) 6. [PowerShell Implementation](#powershell-implementation) 7. [Advanced Features](#advanced-features) 8. [Security Considerations](#security-considerations) 9. [Troubleshooting](#troubleshooting) 10. [Best Practices](#best-practices)Introduction
Email automation in scripts is a crucial component of modern system administration and application monitoring. It enables automatic notification of system events, error alerts, backup completion status, and various other operational updates without manual intervention. This comprehensive guide covers multiple approaches to implementing email notifications across different scripting environments.
Email automation serves several purposes: - System monitoring and alerting - Automated reporting - Error notification and debugging - Backup and maintenance status updates - User notification systems - Integration with CI/CD pipelines
Email Protocols Overview
Understanding email protocols is fundamental to implementing automated email notifications effectively.
SMTP (Simple Mail Transfer Protocol)
SMTP is the standard protocol for sending emails. It operates on specific ports and requires proper authentication.
| Protocol | Port | Security | Description | |----------|------|----------|-------------| | SMTP | 25 | None | Standard unencrypted SMTP | | SMTP | 587 | STARTTLS | Submission port with encryption | | SMTPS | 465 | SSL/TLS | SMTP over SSL | | SMTP | 2587 | STARTTLS | Alternative submission port |
Common Email Providers Configuration
| Provider | SMTP Server | Port | Authentication | |----------|-------------|------|----------------| | Gmail | smtp.gmail.com | 587 | App Password Required | | Outlook | smtp-mail.outlook.com | 587 | OAuth2 or App Password | | Yahoo | smtp.mail.yahoo.com | 587 | App Password Required | | Exchange | mail.company.com | 587 | Domain Authentication |
Setting Up Email Configuration
Gmail Configuration
For Gmail, you need to enable 2-factor authentication and create an app-specific password:
1. Enable 2-Factor Authentication 2. Generate App Password 3. Use app password instead of regular password
Environment Variables Setup
Create a configuration file or use environment variables to store sensitive information:
`bash
Email configuration
export SMTP_SERVER="smtp.gmail.com" export SMTP_PORT="587" export EMAIL_USER="your-email@gmail.com" export EMAIL_PASS="your-app-password" export FROM_EMAIL="notifications@company.com" export TO_EMAIL="admin@company.com"`Python Implementation
Python provides robust libraries for email automation through the built-in smtplib and email modules.
Basic Email Function
`python
import smtplib
import ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
import os
from datetime import datetime
import logging
class EmailNotifier: def __init__(self, smtp_server, smtp_port, username, password): """ Initialize email notifier with SMTP configuration Args: smtp_server (str): SMTP server address smtp_port (int): SMTP server port username (str): Email username password (str): Email password or app password """ self.smtp_server = smtp_server self.smtp_port = smtp_port self.username = username self.password = password self.logger = self._setup_logging() def _setup_logging(self): """Setup logging for email operations""" logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) return logging.getLogger(__name__) def send_email(self, to_email, subject, body, attachments=None, is_html=False): """ Send email with optional attachments Args: to_email (str or list): Recipient email address(es) subject (str): Email subject body (str): Email body content attachments (list): List of file paths to attach is_html (bool): Whether body is HTML formatted Returns: bool: True if email sent successfully, False otherwise """ try: # Create message container msg = MIMEMultipart() msg['From'] = self.username msg['Subject'] = subject # Handle multiple recipients if isinstance(to_email, list): msg['To'] = ', '.join(to_email) recipients = to_email else: msg['To'] = to_email recipients = [to_email] # Add timestamp to body timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") body_with_timestamp = f"{body}\n\nSent at: {timestamp}" # Attach body if is_html: msg.attach(MIMEText(body_with_timestamp, 'html')) else: msg.attach(MIMEText(body_with_timestamp, 'plain')) # Add attachments if provided if attachments: for file_path in attachments: if os.path.isfile(file_path): self._attach_file(msg, file_path) else: self.logger.warning(f"Attachment not found: {file_path}") # Create secure connection and send email context = ssl.create_default_context() with smtplib.SMTP(self.smtp_server, self.smtp_port) as server: server.starttls(context=context) server.login(self.username, self.password) server.sendmail(self.username, recipients, msg.as_string()) self.logger.info(f"Email sent successfully to {recipients}") return True except Exception as e: self.logger.error(f"Failed to send email: {str(e)}") return False def _attach_file(self, msg, file_path): """ Attach file to email message Args: msg: Email message object file_path (str): Path to file to attach """ with open(file_path, "rb") as attachment: part = MIMEBase('application', 'octet-stream') part.set_payload(attachment.read()) encoders.encode_base64(part) part.add_header( 'Content-Disposition', f'attachment; filename= {os.path.basename(file_path)}' ) msg.attach(part) def send_system_alert(self, alert_type, message, severity="INFO"): """ Send system alert with predefined formatting Args: alert_type (str): Type of alert (ERROR, WARNING, INFO) message (str): Alert message severity (str): Severity level """ subject = f"[{severity}] System Alert: {alert_type}" body = f""" System Alert Notification
Alert Type: {alert_type} Severity: {severity} Message: {message} Hostname: {os.uname().nodename if hasattr(os, 'uname') else 'Unknown'} Timestamp: {datetime.now().isoformat()}
This is an automated message from your monitoring system. """ # Determine recipients based on severity recipients = self._get_recipients_by_severity(severity) return self.send_email(recipients, subject, body.strip()) def _get_recipients_by_severity(self, severity): """Get email recipients based on alert severity""" recipients = { "CRITICAL": ["admin@company.com", "oncall@company.com"], "ERROR": ["admin@company.com"], "WARNING": ["admin@company.com"], "INFO": ["notifications@company.com"] } return recipients.get(severity, ["admin@company.com"])
Configuration and usage example
def main(): # Load configuration from environment variables smtp_server = os.getenv('SMTP_SERVER', 'smtp.gmail.com') smtp_port = int(os.getenv('SMTP_PORT', '587')) email_user = os.getenv('EMAIL_USER') email_pass = os.getenv('EMAIL_PASS') if not all([email_user, email_pass]): print("Error: EMAIL_USER and EMAIL_PASS environment variables required") return # Initialize email notifier notifier = EmailNotifier(smtp_server, smtp_port, email_user, email_pass) # Example usage success = notifier.send_email( to_email="recipient@example.com", subject="Test Notification", body="This is a test email from the automated notification system." ) if success: print("Email sent successfully") else: print("Failed to send email")if __name__ == "__main__":
main()
`
Advanced Python Email Templates
`python
class EmailTemplates:
"""Predefined email templates for common notifications"""
@staticmethod
def backup_completion_template(backup_name, status, size, duration):
"""Template for backup completion notifications"""
if status.lower() == 'success':
subject = f"Backup Completed Successfully: {backup_name}"
body = f"""
Backup Operation Completed
Backup Name: {backup_name} Status: SUCCESS Size: {size} Duration: {duration} Completion Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
All files have been successfully backed up. """ else: subject = f"Backup Failed: {backup_name}" body = f""" Backup Operation Failed
Backup Name: {backup_name} Status: FAILED Error Details: {size} # Reusing parameter for error message Duration: {duration} Failure Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
Please check the backup system immediately. """ return subject, body.strip() @staticmethod def disk_space_alert_template(filesystem, usage_percent, available_space): """Template for disk space alerts""" subject = f"Disk Space Alert: {filesystem} at {usage_percent}%" if usage_percent >= 95: severity = "CRITICAL" elif usage_percent >= 85: severity = "WARNING" else: severity = "INFO" body = f""" Disk Space Alert
Filesystem: {filesystem} Usage: {usage_percent}% Available Space: {available_space} Severity: {severity} Check Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
{'IMMEDIATE ACTION REQUIRED!' if usage_percent >= 95 else 'Please monitor closely.'}
"""
return subject, body.strip()
`
Bash Shell Implementation
Bash scripts can send emails using various methods including mail, sendmail, and curl.
Using mail command
`bash
#!/bin/bash
Email configuration
SMTP_SERVER="${SMTP_SERVER:-smtp.gmail.com}" SMTP_PORT="${SMTP_PORT:-587}" FROM_EMAIL="${FROM_EMAIL:-notifications@company.com}" TO_EMAIL="${TO_EMAIL:-admin@company.com}"Function to send simple email using mail command
send_simple_email() { local to_email="$1" local subject="$2" local body="$3" local attachment="$4" # Check if mail command is available if ! command -v mail &> /dev/null; then echo "Error: mail command not found. Install mailutils package." return 1 fi # Send email with or without attachment if [ -n "$attachment" ] && [ -f "$attachment" ]; then echo "$body" | mail -s "$subject" -A "$attachment" "$to_email" else echo "$body" | mail -s "$subject" "$to_email" fi local exit_code=$? if [ $exit_code -eq 0 ]; then echo "Email sent successfully to $to_email" log_email_event "SUCCESS" "$to_email" "$subject" else echo "Failed to send email to $to_email" log_email_event "FAILED" "$to_email" "$subject" fi return $exit_code }Function to send email using curl (more reliable for external SMTP)
send_email_curl() { local to_email="$1" local subject="$2" local body="$3" local attachment="$4" # Temporary file for email content local email_file="/tmp/email_$.txt" local boundary="boundary-$(date +%s)" # Create email headers and body cat > "$email_file" << EOF From: $FROM_EMAIL To: $to_email Subject: $subject Date: $(date -R) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="$boundary"--$boundary Content-Type: text/plain; charset=UTF-8
$body
Sent at: $(date '+%Y-%m-%d %H:%M:%S') EOF
# Add attachment if provided if [ -n "$attachment" ] && [ -f "$attachment" ]; then cat >> "$email_file" << EOF
--$boundary Content-Type: application/octet-stream Content-Disposition: attachment; filename="$(basename "$attachment")" Content-Transfer-Encoding: base64
$(base64 "$attachment") EOF fi echo "--$boundary--" >> "$email_file" # Send email using curl curl --url "smtp://$SMTP_SERVER:$SMTP_PORT" \ --ssl-reqd \ --mail-from "$FROM_EMAIL" \ --mail-rcpt "$to_email" \ --upload-file "$email_file" \ --user "$EMAIL_USER:$EMAIL_PASS" \ --silent local exit_code=$? # Clean up temporary file rm -f "$email_file" if [ $exit_code -eq 0 ]; then echo "Email sent successfully to $to_email using curl" log_email_event "SUCCESS" "$to_email" "$subject" else echo "Failed to send email to $to_email using curl" log_email_event "FAILED" "$to_email" "$subject" fi return $exit_code }
Function to log email events
log_email_event() { local status="$1" local recipient="$2" local subject="$3" local log_file="/var/log/email_notifications.log" # Create log directory if it doesn't exist mkdir -p "$(dirname "$log_file")" # Log the event echo "$(date '+%Y-%m-%d %H:%M:%S') - $status - To: $recipient - Subject: $subject" >> "$log_file" }Function to send system alert
send_system_alert() { local alert_type="$1" local message="$2" local severity="${3:-INFO}" local subject="[$severity] System Alert: $alert_type" local body="System Alert NotificationAlert Type: $alert_type Severity: $severity Message: $message Hostname: $(hostname) Timestamp: $(date '+%Y-%m-%d %H:%M:%S')
This is an automated message from your monitoring system." # Determine recipients based on severity local recipients case "$severity" in "CRITICAL") recipients="admin@company.com,oncall@company.com" ;; "ERROR"|"WARNING") recipients="admin@company.com" ;; *) recipients="notifications@company.com" ;; esac # Send to multiple recipients IFS=',' read -ra ADDR <<< "$recipients" for recipient in "${ADDR[@]}"; do send_email_curl "$recipient" "$subject" "$body" done }
Function to monitor disk space and send alerts
monitor_disk_space() { local threshold="${1:-85}" # Get disk usage information df -h | grep -vE '^Filesystem|tmpfs|cdrom' | awk '{ print $5 " " $1 " " $4 }' | while read -r output; do usage=$(echo "$output" | awk '{ print $1}' | cut -d'%' -f1) partition=$(echo "$output" | awk '{ print $2 }') available=$(echo "$output" | awk '{ print $3 }') if [ "$usage" -ge "$threshold" ]; then local severity if [ "$usage" -ge 95 ]; then severity="CRITICAL" elif [ "$usage" -ge 85 ]; then severity="WARNING" else severity="INFO" fi local alert_message="Disk usage on $partition is at $usage%. Available space: $available" send_system_alert "Disk Space Alert" "$alert_message" "$severity" fi done }Function to check service status and send alerts
check_service_status() { local service_name="$1" if systemctl is-active --quiet "$service_name"; then echo "Service $service_name is running" else local alert_message="Service $service_name is not running on $(hostname)" send_system_alert "Service Down" "$alert_message" "ERROR" fi }Main execution example
main() { echo "Starting automated email notification system..." # Check required environment variables if [ -z "$EMAIL_USER" ] || [ -z "$EMAIL_PASS" ]; then echo "Error: EMAIL_USER and EMAIL_PASS environment variables are required" exit 1 fi # Example usage case "${1:-test}" in "test") send_email_curl "$TO_EMAIL" "Test Notification" "This is a test email from the bash notification system." ;; "disk") monitor_disk_space 80 ;; "service") check_service_status "${2:-apache2}" ;; "alert") send_system_alert "${2:-Test Alert}" "${3:-This is a test alert}" "${4:-INFO}" ;; *) echo "Usage: $0 {test|disk|serviceExecute main function with all arguments
main "$@"`PowerShell Implementation
PowerShell provides the Send-MailMessage cmdlet and .NET classes for email functionality.
PowerShell Email Module
`powershell
Email notification module for PowerShell
Email configuration class
class EmailConfig { [string]$SmtpServer [int]$SmtpPort [string]$Username [string]$Password [string]$FromEmail [bool]$UseSSL EmailConfig([string]$server, [int]$port, [string]$user, [string]$pass, [string]$from) { $this.SmtpServer = $server $this.SmtpPort = $port $this.Username = $user $this.Password = $pass $this.FromEmail = $from $this.UseSSL = $true } }Email notifier class
class EmailNotifier { [EmailConfig]$Config [string]$LogPath EmailNotifier([EmailConfig]$config) { $this.Config = $config $this.LogPath = "C:\Logs\EmailNotifications.log" $this.EnsureLogDirectory() } [void]EnsureLogDirectory() { $logDir = Split-Path -Parent $this.LogPath if (-not (Test-Path -Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } } [bool]SendEmail([string[]]$ToEmail, [string]$Subject, [string]$Body, [string[]]$Attachments, [bool]$IsHtml) { try { # Create secure password $securePassword = ConvertTo-SecureString -String $this.Config.Password -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential($this.Config.Username, $securePassword) # Add timestamp to body $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $bodyWithTimestamp = "$BodynnSent at: $timestamp"
# Prepare email parameters
$emailParams = @{
SmtpServer = $this.Config.SmtpServer
Port = $this.Config.SmtpPort
From = $this.Config.FromEmail
To = $ToEmail
Subject = $Subject
Body = $bodyWithTimestamp
Credential = $credential
UseSsl = $this.Config.UseSSL
}
# Add HTML formatting if specified
if ($IsHtml) {
$emailParams.BodyAsHtml = $true
}
# Add attachments if provided
if ($Attachments -and $Attachments.Count -gt 0) {
$validAttachments = @()
foreach ($attachment in $Attachments) {
if (Test-Path -Path $attachment) {
$validAttachments += $attachment
} else {
Write-Warning "Attachment not found: $attachment"
}
}
if ($validAttachments.Count -gt 0) {
$emailParams.Attachments = $validAttachments
}
}
# Send email
Send-MailMessage @emailParams
$this.LogEmailEvent("SUCCESS", ($ToEmail -join ", "), $Subject)
Write-Host "Email sent successfully to: $($ToEmail -join ', ')" -ForegroundColor Green
return $true
} catch {
$this.LogEmailEvent("FAILED", ($ToEmail -join ", "), $Subject, $_.Exception.Message)
Write-Error "Failed to send email: $($_.Exception.Message)"
return $false
}
}
[bool]SendEmail([string[]]$ToEmail, [string]$Subject, [string]$Body) {
return $this.SendEmail($ToEmail, $Subject, $Body, @(), $false)
}
[void]LogEmailEvent([string]$Status, [string]$Recipients, [string]$Subject, [string]$Error = "") {
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "$timestamp - $Status - To: $Recipients - Subject: $Subject"
if ($Error) {
$logEntry += " - Error: $Error"
}
Add-Content -Path $this.LogPath -Value $logEntry
}
[bool]SendSystemAlert([string]$AlertType, [string]$Message, [string]$Severity = "INFO") {
$subject = "[$Severity] System Alert: $AlertType"
$body = @"
System Alert NotificationAlert Type: $AlertType Severity: $Severity Message: $Message Hostname: $env:COMPUTERNAME Timestamp: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
This is an automated message from your monitoring system. "@ $recipients = $this.GetRecipientsBySeverity($Severity) return $this.SendEmail($recipients, $subject, $body) } [string[]]GetRecipientsBySeverity([string]$Severity) { switch ($Severity) { "CRITICAL" { return @("admin@company.com", "oncall@company.com") } "ERROR" { return @("admin@company.com") } "WARNING" { return @("admin@company.com") } default { return @("notifications@company.com") } } } }
Email templates class
class EmailTemplates { static [hashtable]BackupCompletionTemplate([string]$BackupName, [string]$Status, [string]$Size, [string]$Duration) { if ($Status.ToLower() -eq 'success') { $subject = "Backup Completed Successfully: $BackupName" $body = @" Backup Operation CompletedBackup Name: $BackupName Status: SUCCESS Size: $Size Duration: $Duration Completion Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
All files have been successfully backed up. "@ } else { $subject = "Backup Failed: $BackupName" $body = @" Backup Operation Failed
Backup Name: $BackupName Status: FAILED Error Details: $Size Duration: $Duration Failure Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
Please check the backup system immediately. "@ } return @{ Subject = $subject Body = $body } } static [hashtable]DiskSpaceAlertTemplate([string]$DriveLetter, [int]$UsagePercent, [string]$AvailableSpace) { $subject = "Disk Space Alert: Drive $DriveLetter at $UsagePercent%" $severity = switch ($UsagePercent) { {$_ -ge 95} { "CRITICAL" } {$_ -ge 85} { "WARNING" } default { "INFO" } } $actionRequired = if ($UsagePercent -ge 95) { "IMMEDIATE ACTION REQUIRED!" } else { "Please monitor closely." } $body = @" Disk Space Alert
Drive: $DriveLetter Usage: $UsagePercent% Available Space: $AvailableSpace Severity: $severity Check Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
$actionRequired "@ return @{ Subject = $subject Body = $body } } }
Utility functions for system monitoring
function Monitor-DiskSpace { param( [EmailNotifier]$Notifier, [int]$Threshold = 85 ) Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 } | ForEach-Object { $usagePercent = [math]::Round((($_.Size - $_.FreeSpace) / $_.Size) * 100, 2) $availableGB = [math]::Round($_.FreeSpace / 1GB, 2) if ($usagePercent -ge $Threshold) { $severity = switch ($usagePercent) { {$_ -ge 95} { "CRITICAL" } {$_ -ge 85} { "WARNING" } default { "INFO" } } $template = [EmailTemplates]::DiskSpaceAlertTemplate($_.DeviceID, $usagePercent, "$availableGB GB") $Notifier.SendEmail($Notifier.GetRecipientsBySeverity($severity), $template.Subject, $template.Body) } } }function Test-ServiceStatus { param( [EmailNotifier]$Notifier, [string]$ServiceName ) $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue if (-not $service) { $message = "Service '$ServiceName' not found on $env:COMPUTERNAME" $Notifier.SendSystemAlert("Service Not Found", $message, "ERROR") } elseif ($service.Status -ne 'Running') { $message = "Service '$ServiceName' is $($service.Status) on $env:COMPUTERNAME" $Notifier.SendSystemAlert("Service Down", $message, "ERROR") } else { Write-Host "Service $ServiceName is running normally" -ForegroundColor Green } }
Main execution function
function Start-EmailNotificationSystem { param( [string]$Action = "test", [string]$Parameter1 = "", [string]$Parameter2 = "", [string]$Parameter3 = "" ) # Load configuration from environment variables $smtpServer = $env:SMTP_SERVER ?? "smtp.gmail.com" $smtpPort = [int]($env:SMTP_PORT ?? "587") $emailUser = $env:EMAIL_USER $emailPass = $env:EMAIL_PASS $fromEmail = $env:FROM_EMAIL ?? $emailUser $toEmail = $env:TO_EMAIL ?? "admin@company.com" if (-not $emailUser -or -not $emailPass) { Write-Error "EMAIL_USER and EMAIL_PASS environment variables are required" return } # Initialize email configuration and notifier $config = [EmailConfig]::new($smtpServer, $smtpPort, $emailUser, $emailPass, $fromEmail) $notifier = [EmailNotifier]::new($config) # Execute based on action parameter switch ($Action.ToLower()) { "test" { $success = $notifier.SendEmail(@($toEmail), "Test Notification", "This is a test email from the PowerShell notification system.") if ($success) { Write-Host "Test email sent successfully" -ForegroundColor Green } } "disk" { $threshold = if ($Parameter1) { [int]$Parameter1 } else { 80 } Monitor-DiskSpace -Notifier $notifier -Threshold $threshold } "service" { if (-not $Parameter1) { Write-Error "Service name parameter required" return } Test-ServiceStatus -Notifier $notifier -ServiceName $Parameter1 } "alert" { if (-not $Parameter1 -or -not $Parameter2) { Write-Error "Alert type and message parameters required" return } $severity = if ($Parameter3) { $Parameter3 } else { "INFO" } $notifier.SendSystemAlert($Parameter1, $Parameter2, $severity) } default { Write-Host "Usage: Start-EmailNotificationSystem -Action {test|disk|service|alert} [parameters]" Write-Host "Examples:" Write-Host " Start-EmailNotificationSystem -Action test" Write-Host " Start-EmailNotificationSystem -Action disk -Parameter1 80" Write-Host " Start-EmailNotificationSystem -Action service -Parameter1 'Spooler'" Write-Host " Start-EmailNotificationSystem -Action alert -Parameter1 'Test Alert' -Parameter2 'This is a test' -Parameter3 'INFO'" } } }Export functions for module use
Export-ModuleMember -Function Start-EmailNotificationSystem Export-ModuleMember -Class EmailNotifier, EmailConfig, EmailTemplates`Advanced Features
Email Rate Limiting
Implement rate limiting to prevent email spam:
`python
import time
from collections import defaultdict
class RateLimitedEmailNotifier(EmailNotifier):
def __init__(self, args, *kwargs):
super().__init__(args, *kwargs)
self.email_counts = defaultdict(list)
self.rate_limits = {
'per_minute': 10,
'per_hour': 100,
'per_day': 500
}
def _check_rate_limit(self, recipient):
now = time.time()
times = self.email_counts[recipient]
# Remove old entries
times[:] = [t for t in times if now - t < 86400] # Keep last 24 hours
# Check limits
minute_count = len([t for t in times if now - t < 60])
hour_count = len([t for t in times if now - t < 3600])
day_count = len(times)
if (minute_count >= self.rate_limits['per_minute'] or
hour_count >= self.rate_limits['per_hour'] or
day_count >= self.rate_limits['per_day']):
return False
return True
def send_email(self, to_email, subject, body, kwargs):
recipients = [to_email] if isinstance(to_email, str) else to_email
for recipient in recipients:
if not self._check_rate_limit(recipient):
self.logger.warning(f"Rate limit exceeded for {recipient}")
return False
success = super().send_email(to_email, subject, body, kwargs)
if success:
now = time.time()
for recipient in recipients:
self.email_counts[recipient].append(now)
return success
`
Email Queue Management
`python
import queue
import threading
import json
from datetime import datetime
class EmailQueue:
def __init__(self, notifier, max_workers=3):
self.notifier = notifier
self.email_queue = queue.Queue()
self.max_workers = max_workers
self.workers = []
self.running = False
self.failed_emails_file = "failed_emails.json"
def start(self):
self.running = True
for i in range(self.max_workers):
worker = threading.Thread(target=self._worker, name=f"EmailWorker-{i}")
worker.daemon = True
worker.start()
self.workers.append(worker)
def stop(self):
self.running = False
# Add sentinel values to wake up workers
for _ in range(self.max_workers):
self.email_queue.put(None)
def _worker(self):
while self.running:
try:
email_data = self.email_queue.get(timeout=1)
if email_data is None: # Sentinel value
break
success = self.notifier.send_email(email_data['params'])
if not success:
self._save_failed_email(email_data)
self.email_queue.task_done()
except queue.Empty:
continue
except Exception as e:
print(f"Worker error: {e}")
def queue_email(self, priority=1, email_params):
email_data = {
'timestamp': datetime.now().isoformat(),
'priority': priority,
'params': email_params
}
self.email_queue.put(email_data)
def _save_failed_email(self, email_data):
try:
failed_emails = []
try:
with open(self.failed_emails_file, 'r') as f:
failed_emails = json.load(f)
except FileNotFoundError:
pass
failed_emails.append(email_data)
with open(self.failed_emails_file, 'w') as f:
json.dump(failed_emails, f, indent=2)
except Exception as e:
print(f"Failed to save failed email: {e}")
`
Security Considerations
Secure Credential Storage
| Method | Security Level | Use Case | |--------|----------------|----------| | Environment Variables | Medium | Development/Testing | | Encrypted Configuration Files | High | Production Systems | | Key Management Services | Very High | Enterprise Applications | | OAuth2 Tokens | High | Cloud Services |
Best Security Practices
1. Use App-Specific Passwords: Never use main account passwords 2. Implement TLS/SSL: Always encrypt email transmission 3. Rotate Credentials: Regularly update passwords and tokens 4. Limit Permissions: Use accounts with minimal required privileges 5. Monitor Usage: Log and monitor email sending patterns
Troubleshooting
Common Issues and Solutions
| Issue | Possible Cause | Solution | |-------|----------------|----------| | Authentication Failed | Wrong credentials | Verify username/password, use app password | | Connection Timeout | Firewall/Network | Check network connectivity, firewall rules | | SSL Certificate Error | Certificate issues | Update certificates, verify server settings | | Rate Limiting | Too many emails | Implement rate limiting, use multiple accounts | | Large Attachment Failure | Size limits | Compress files, use file sharing links |
Debugging Commands
`bash
Test SMTP connectivity
telnet smtp.gmail.com 587Check DNS resolution
nslookup smtp.gmail.comTest with openssl
openssl s_client -connect smtp.gmail.com:587 -starttls smtpVerify certificate
openssl s_client -showcerts -connect smtp.gmail.com:587`Best Practices
Email Content Guidelines
1. Clear Subject Lines: Include severity and system information 2. Structured Body: Use consistent formatting for alerts 3. Include Context: Add timestamp, hostname, and relevant details 4. Action Items: Clearly state required actions 5. Contact Information: Include escalation contacts
Performance Optimization
1. Connection Pooling: Reuse SMTP connections when possible 2. Batch Processing: Group emails to same recipients 3. Asynchronous Sending: Use queues for non-critical emails 4. Template Caching: Pre-compile email templates 5. Attachment Optimization: Compress large files
Monitoring and Maintenance
1. Log All Activities: Track sent, failed, and queued emails 2. Monitor Delivery Rates: Track bounce rates and failures 3. Regular Testing: Verify email functionality periodically 4. Update Dependencies: Keep email libraries current 5. Backup Configurations: Maintain configuration backups
This comprehensive guide provides the foundation for implementing robust email notification systems across multiple scripting environments, ensuring reliable automated communications for system monitoring and alerting needs.