
Automating Notifications
In today's fast-paced world, staying on top of information is crucial. Whether you're monitoring a long-running script, tracking system metrics, or just want to be alerted when specific events occur, automating notifications can save you time and keep you informed. Python offers several excellent libraries to help you send alerts through various channels like email, SMS, or even messaging apps. Let's explore how you can set up automated notifications for your projects.
Why Automate Notifications?
Automating notifications eliminates the need for constant manual checking. Imagine you have a data processing script that takes hours to run. Instead of periodically checking if it's finished, you can set up a notification to alert you when the task is complete. This is especially useful for:
- Long-running computations or data processing jobs
- System monitoring and alerting for errors or thresholds
- Daily/weekly report generation
- Website status monitoring
- Backup completion alerts
Automating notifications not only saves time but also ensures you never miss important events. You can respond to issues immediately rather than discovering them hours or days later.
Email Notifications
Email remains one of the most universal notification methods. Python's smtplib
module makes it easy to send emails programmatically. Here's a basic example:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
def send_email(subject, body, to_email):
# Your email credentials
from_email = "your_email@gmail.com"
password = "your_app_password" # Use app-specific password for Gmail
# Create message
msg = MIMEMultipart()
msg['From'] = from_email
msg['To'] = to_email
msg['Subject'] = subject
# Attach body
msg.attach(MIMEText(body, 'plain'))
# Send email
try:
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(from_email, password)
text = msg.as_string()
server.sendmail(from_email, to_email, text)
server.quit()
print("Email sent successfully!")
except Exception as e:
print(f"Failed to send email: {e}")
# Example usage
send_email("Task Completed", "Your data processing is finished!", "recipient@example.com")
For security reasons, it's recommended to use environment variables or configuration files to store your email credentials rather than hardcoding them.
Notification Method | Setup Complexity | Cost | Delivery Speed | Reliability |
---|---|---|---|---|
Low | Free | Moderate | High | |
SMS | Medium | Paid | Fast | High |
Push Notifications | Medium | Free | Instant | High |
Webhooks | High | Free | Instant | Medium |
SMS Notifications
For time-sensitive alerts, SMS notifications can be more effective than email. Several services provide APIs for sending SMS messages. Twilio is one of the most popular options:
from twilio.rest import Client
def send_sms(message, to_number):
# Your Twilio credentials
account_sid = 'your_account_sid'
auth_token = 'your_auth_token'
from_number = 'your_twilio_number'
client = Client(account_sid, auth_token)
try:
message = client.messages.create(
body=message,
from_=from_number,
to=to_number
)
print(f"SMS sent! Message SID: {message.sid}")
except Exception as e:
print(f"Failed to send SMS: {e}")
# Example usage
send_sms("Server is down! Immediate attention required.", "+1234567890")
Remember that SMS services typically charge per message, so this method is best reserved for critical alerts rather than routine notifications.
When choosing a notification method, consider these factors: - Urgency of the information - Audience preferences and accessibility - Cost constraints - Reliability requirements - Integration complexity with existing systems
Push Notifications
Push notifications are excellent for mobile alerts. Services like Pushbullet or Pushover make it easy to send notifications to your mobile devices:
import requests
def push_notification(title, message):
# Pushbullet API
api_token = 'your_access_token'
url = 'https://api.pushbullet.com/v2/pushes'
headers = {
'Access-Token': api_token,
'Content-Type': 'application/json'
}
data = {
'type': 'note',
'title': title,
'body': message
}
try:
response = requests.post(url, json=data, headers=headers)
if response.status_code == 200:
print("Push notification sent!")
else:
print(f"Failed: {response.text}")
except Exception as e:
print(f"Error: {e}")
# Example usage
push_notification("Backup Complete", "The nightly backup finished successfully.")
Push notifications offer near-instant delivery and high visibility, making them ideal for time-sensitive information.
Desktop Notifications
For local machine alerts, you can use desktop notifications that appear as system toast messages:
from plyer import notification
import time
def desktop_alert(title, message):
notification.notify(
title=title,
message=message,
app_name="Python Notifier",
timeout=10 # seconds
)
# Example: Monitor CPU usage and alert if high
# (pseudo-code for illustration)
while True:
cpu_usage = get_cpu_usage() # You'd implement this
if cpu_usage > 90:
desktop_alert("High CPU Usage", f"CPU at {cpu_usage}%")
time.sleep(300) # Check every 5 minutes
The plyer
library provides a cross-platform interface for various system features, including notifications.
Notification Type | Best For | Limitations | Setup Time |
---|---|---|---|
Detailed reports, non-urgent updates | May be overlooked in crowded inboxes | 15 minutes | |
SMS | Critical, time-sensitive alerts | Cost per message, character limits | 30 minutes |
Push | Mobile alerts, quick updates | Requires app installation | 45 minutes |
Desktop | Local machine monitoring | Only works on user's active session | 10 minutes |
Scheduling Regular Notifications
Sometimes you want notifications to be sent at regular intervals, such as daily reports or weekly summaries. The schedule
library is perfect for this:
import schedule
import time
from datetime import datetime
def daily_report():
# Generate and send your daily report
report_data = generate_daily_stats() # Your function
send_email("Daily Report", report_data, "team@example.com")
def weekly_summary():
# Generate and send weekly summary
summary = generate_weekly_summary() # Your function
send_email("Weekly Summary", summary, "manager@example.com")
# Schedule tasks
schedule.every().day.at("09:00").do(daily_report)
schedule.every().monday.at("10:00").do(weekly_summary)
print("Scheduler started...")
while True:
schedule.run_pending()
time.sleep(60) # Check every minute
This approach lets you automate routine reporting without manual intervention, ensuring consistency and timeliness.
Error Handling and Logging
When building notification systems, robust error handling is crucial. You don't want your notification system to fail silently:
import logging
# Set up logging
logging.basicConfig(
filename='notifications.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def send_notification_with_retry(notification_func, *args, max_retries=3):
for attempt in range(max_retries):
try:
notification_func(*args)
logging.info("Notification sent successfully")
return True
except Exception as e:
logging.error(f"Attempt {attempt + 1} failed: {str(e)}")
if attempt < max_retries - 1:
time.sleep(2 ** attempt) # Exponential backoff
else:
logging.critical("All retry attempts failed")
return False
# Example usage
send_notification_with_retry(send_email, "Test Subject", "Test Body", "test@example.com")
This retry mechanism with exponential backoff helps handle temporary network issues or service interruptions.
Common notification pitfalls to avoid: - Over-notifying - sending too many alerts leads to notification fatigue - Inadequate error handling - failures going unnoticed - Hardcoded credentials - security risks - No logging - difficult troubleshooting - Single point of failure - relying on one notification method
Advanced: Conditional Notifications
Sometimes you only want to send notifications when certain conditions are met. This prevents unnecessary alerts:
def monitor_disk_space():
import shutil
total, used, free = shutil.disk_usage("/")
free_percent = (free / total) * 100
# Only alert if disk space is critically low
if free_percent < 10:
message = f"Warning: Disk space critically low. Only {free_percent:.1f}% free."
send_email("Disk Space Alert", message, "admin@example.com")
push_notification("Disk Alert", message)
return free_percent
# Run check every hour
schedule.every().hour.do(monitor_disk_space)
Conditional notifications ensure you only receive alerts when action is actually required, reducing noise and increasing the signal-to-noise ratio.
Condition Type | Example Scenario | Notification Action |
---|---|---|
Threshold | CPU > 90% | Send immediate alert |
State Change | Service down → up | Send recovery notice |
Time-based | Daily at 9 AM | Send routine report |
Event-based | New user signup | Send welcome message |
Integrating Multiple Notification Channels
For important alerts, you might want to use multiple channels to ensure the message is received:
def multi_channel_alert(title, message, urgency="medium"):
# Always send email for record-keeping
send_email(title, message, "alerts@example.com")
if urgency == "high":
send_sms(message, "+1234567890")
push_notification(title, message)
elif urgency == "critical":
# All channels for critical alerts
send_sms(message, "+1234567890")
push_notification(title, message)
desktop_alert(title, message)
# Potentially add phone call here
# Example usage
multi_channel_alert("Database Error", "Primary database connection failed", "critical")
This escalation strategy ensures that critical alerts receive maximum attention while routine notifications use less intrusive methods.
Testing Your Notification System
Before relying on your automated notifications, thorough testing is essential:
def test_notification_system():
"""Test all notification channels"""
test_cases = [
(send_email, "Test Email", "This is a test email", "test@example.com"),
(push_notification, "Test Push", "This is a test push"),
# Add other notification functions
]
for test_func, *args in test_cases:
try:
success = test_func(*args)
if success:
print(f"✓ {test_func.__name__} test passed")
else:
print(f"✗ {test_func.__name__} test failed")
except Exception as e:
print(f"✗ {test_func.__name__} test error: {e}")
# Run tests during development
test_notification_system()
Regular testing ensures your notification system remains reliable and functional as your code evolves.
Best practices for notification systems: - Test thoroughly before deployment - Implement retry logic for failed deliveries - Use appropriate channels for different urgency levels - Monitor your monitoring - ensure the system itself is working - Review and adjust frequency based on feedback - Secure credentials properly - Document procedures for different alert types
Real-world Example: Website Monitoring
Let's create a complete website monitoring script with notifications:
import requests
import time
import logging
from requests.exceptions import RequestException
def check_website(url, expected_status=200):
try:
response = requests.get(url, timeout=10)
if response.status_code == expected_status:
return True, f"Website {url} is up"
else:
return False, f"Website {url} returned status {response.status_code}"
except RequestException as e:
return False, f"Website {url} is down: {str(e)}"
def monitor_websites(websites, check_interval=300):
"""Monitor multiple websites with notifications"""
down_status = {} # Track which sites are down
while True:
for url, expected_status in websites.items():
is_up, message = check_website(url, expected_status)
if not is_up and url not in down_status:
# Site just went down
multi_channel_alert("Website Down", message, "high")
down_status[url] = True
logging.error(message)
elif is_up and url in down_status:
# Site came back up
multi_channel_alert("Website Restored", f"{url} is back online", "medium")
del down_status[url]
logging.info(f"{url} restored")
time.sleep(check_interval)
# Websites to monitor with expected status codes
websites_to_monitor = {
"https://example.com": 200,
"https://api.example.com": 200,
"https://status.example.com": 200
}
# Start monitoring
monitor_websites(websites_to_monitor)
This comprehensive example shows how you can build a robust monitoring system that only notifies you when status changes occur, avoiding repetitive alerts for ongoing issues.
Handling Rate Limits and quotas
Many notification services have rate limits or usage quotas. It's important to handle these gracefully:
from functools import wraps
import time
def rate_limited(max_per_minute):
"""Decorator to limit function calls"""
min_interval = 60.0 / max_per_minute
def decorator(func):
last_called = [0.0]
@wraps(func)
def wrapper(*args, **kwargs):
elapsed = time.time() - last_called[0]
left_to_wait = min_interval - elapsed
if left_to_wait > 0:
time.sleep(left_to_wait)
ret = func(*args, **kwargs)
last_called[0] = time.time()
return ret
return wrapper
return decorator
# Apply rate limiting to your notification functions
@rate_limited(60) # Max 60 calls per minute
def send_sms_limited(message, to_number):
return send_sms(message, to_number)
This rate limiting decorator helps you stay within service limits and avoid being blocked for excessive API calls.
Remember that effective notification systems strike a balance between being informative and being intrusive. The goal is to keep users informed without causing alert fatigue. Start with the most critical notifications and expand gradually as you learn what information is truly valuable to receive automatically.
As you implement your notification system, keep refining it based on actual usage patterns and feedback. The best systems evolve over time to become more precise and less intrusive while maintaining their reliability and usefulness.