Status page subscriber notifications let customers opt in to receive alerts when incidents occur or services are degraded. When implemented well, they're a powerful trust-building mechanism: customers who find out about an incident from your notification — before they experience it themselves — report significantly higher satisfaction than customers who discover incidents independently.
Notification Channel Options
Email Notifications
The most universally supported subscriber channel. Email notifications work for all customers regardless of their tech stack or preferences.
Best practices:
- Send plain text or simple HTML — heavy HTML renders poorly in some clients
- Subject line includes severity and service name for scannable inboxes
- Include direct link to status page in every email
- Unsubscribe link in every email (legally required in many jurisdictions)
Webhook Notifications
For technical customers who want to integrate status notifications with their own systems (Slack, JIRA, PagerDuty, custom dashboards):
{
"event_type": "incident_created",
"incident": {
"id": "inc-20250820-001",
"title": "Login Service Degradation",
"status": "investigating",
"severity": "major",
"started_at": "2025-08-20T14:22:00Z",
"affected_components": [
{"name": "Authentication", "status": "partial_outage"}
]
},
"page": {
"name": "Example Service",
"url": "https://status.example.com"
}
}
SMS Notifications
Higher engagement but more expensive and invasive. Best reserved for:
- Enterprise customers on dedicated plans
- Opt-in for highest-severity incidents only
- Time-sensitive services where missed emails matter
Subscription Management
What Customers Should Be Able to Subscribe To
Not every customer cares about every component. Allow granular subscriptions:
# Subscription model
class SubscriberSubscription:
"""
Track what a subscriber has opted in to receive.
"""
subscriber_email: str
subscribed_to_all: bool # Subscribe to all components
component_subscriptions: list # Specific component IDs if not all
incident_severity_filter: str # "all", "major_only", "critical_only"
notification_channels: list # ["email", "webhook", "sms"]
webhook_url: str # If webhook subscription
phone_number: str # If SMS subscription
# Preferences
notify_on_create: bool # When incident first created
notify_on_update: bool # On each status update
notify_on_resolve: bool # When resolved
notify_on_maintenance: bool # Scheduled maintenance
# Unsubscribe token (for one-click unsubscribe)
unsubscribe_token: str
def matches_incident(self, incident) -> bool:
"""Check if this subscriber should receive this incident notification."""
# Check severity filter
if self.incident_severity_filter == "critical_only":
if incident.severity not in ["critical", "major"]:
return False
elif self.incident_severity_filter == "major_only":
if incident.severity == "minor":
return False
# Check component match
if not self.subscribed_to_all:
incident_components = set(c.id for c in incident.affected_components)
subscribed_components = set(self.component_subscriptions)
if not incident_components.intersection(subscribed_components):
return False
return True
Notification Timing Strategy
When to send notifications matters as much as what you send:
class NotificationTriggerPolicy:
"""
Define when subscriber notifications should be sent.
"""
def should_notify(self, event_type: str, incident, subscriber) -> dict:
"""
Determine whether to send a notification for a given event.
"""
if event_type == "incident_created":
# Always notify on incident creation if severity is significant
should_send = (
incident.severity in ["critical", "major"]
and subscriber.notify_on_create
and subscriber.matches_incident(incident)
)
# Don't send for minor incidents that might resolve in minutes
if incident.severity == "minor":
should_send = False # Wait to see if it self-resolves
return {
"send": should_send,
"reason": "Significant incident created" if should_send else "Minor incident — wait and see"
}
elif event_type == "incident_updated":
# Only send update if there's a meaningful status change
meaningful_update = incident.status_changed or incident.impact_changed
return {
"send": subscriber.notify_on_update and meaningful_update,
"reason": "Status changed" if meaningful_update else "No meaningful change"
}
elif event_type == "incident_resolved":
# Always send resolution if the subscriber got the initial notification
return {
"send": subscriber.notify_on_resolve and subscriber.received_initial_notification(incident.id),
"reason": "Incident resolved"
}
elif event_type == "maintenance_scheduled":
# Send maintenance notifications well in advance
hours_until_start = (incident.scheduled_start - datetime.utcnow()).total_seconds() / 3600
return {
"send": subscriber.notify_on_maintenance and hours_until_start >= 24,
"reason": "Advance maintenance notice"
}
return {"send": False, "reason": "Unknown event type"}
Email Notification Templates
# notification_templates.py
INCIDENT_CREATED_EMAIL = """
Subject: [Service Status] {severity}: {title}
{service_name} Status Update
Status: {status_display}
Affected: {affected_components}
Started: {started_at_formatted}
{incident_update_text}
Visit our status page for real-time updates:
{status_page_url}
---
You're receiving this because you subscribed to notifications.
To update your notification preferences: {preferences_url}
To unsubscribe: {unsubscribe_url}
"""
INCIDENT_RESOLVED_EMAIL = """
Subject: [Service Status] Resolved: {title}
{service_name} Service Restored
{title} has been resolved.
Duration: {duration_display}
Resolved at: {resolved_at_formatted}
{resolution_summary}
We'll publish a detailed incident report within 5 business days.
View incident history: {status_page_url}
---
To update your notification preferences: {preferences_url}
To unsubscribe: {unsubscribe_url}
"""
MAINTENANCE_NOTIFICATION_EMAIL = """
Subject: [Scheduled Maintenance] {title} — {scheduled_date}
{service_name} Scheduled Maintenance
We're planning maintenance that may affect your service.
When: {scheduled_start} to {scheduled_end} UTC
Expected impact: {expected_impact}
Affected components: {affected_components}
{maintenance_details}
We'll send updates if this affects our timeline.
View full maintenance schedule: {status_page_url}
---
To update preferences: {preferences_url}
To unsubscribe: {unsubscribe_url}
"""
def render_template(template_str, variables):
"""Simple template rendering for notification content."""
result = template_str
for key, value in variables.items():
result = result.replace(f"{{{key}}}", str(value))
return result
Webhook Delivery Best Practices
For webhook subscribers, reliability matters:
class WebhookDelivery:
"""
Reliably deliver webhook notifications with retry logic.
"""
MAX_RETRIES = 3
RETRY_DELAYS = [30, 120, 300] # 30s, 2min, 5min between retries
TIMEOUT_SECONDS = 10
def deliver(self, webhook_url: str, payload: dict, secret: str = None) -> dict:
"""Deliver webhook with retries on failure."""
for attempt in range(self.MAX_RETRIES):
try:
result = self._send_request(webhook_url, payload, secret)
if result["success"]:
return result
# Server returned error — retry
if attempt < self.MAX_RETRIES - 1:
delay = self.RETRY_DELAYS[attempt]
time.sleep(delay)
except Exception as e:
if attempt < self.MAX_RETRIES - 1:
delay = self.RETRY_DELAYS[attempt]
time.sleep(delay)
# All retries failed — mark subscriber webhook as potentially invalid
self.log_delivery_failure(webhook_url)
return {"success": False, "reason": "All retries failed"}
def _send_request(self, url: str, payload: dict, secret: str) -> dict:
"""Make a single webhook HTTP request."""
import json, hmac, hashlib
payload_json = json.dumps(payload)
headers = {"Content-Type": "application/json"}
if secret:
sig = hmac.new(
secret.encode(),
payload_json.encode(),
hashlib.sha256
).hexdigest()
headers["X-Status-Signature"] = f"sha256={sig}"
response = requests.post(
url,
data=payload_json,
headers=headers,
timeout=self.TIMEOUT_SECONDS
)
return {
"success": 200 <= response.status_code < 300,
"status_code": response.status_code
}
Subscriber List Management
Maintain subscriber list hygiene:
def manage_subscriber_health(subscribers):
"""
Identify and handle problematic subscriber records.
"""
issues = {
"hard_bounced_emails": [],
"consistently_failing_webhooks": [],
"unsubscribed": [],
"inactive_long_term": []
}
for subscriber in subscribers:
# Email hard bounces — remove immediately
if subscriber.email_bounce_type == "hard":
issues["hard_bounced_emails"].append(subscriber.id)
# Webhooks failing consistently for > 7 days
if subscriber.webhook_failure_streak_days > 7:
issues["consistently_failing_webhooks"].append({
"subscriber_id": subscriber.id,
"webhook_url": subscriber.webhook_url,
"failure_days": subscriber.webhook_failure_streak_days
})
# Subscribers who haven't engaged in 2 years
if subscriber.last_notification_opened and \
(datetime.utcnow() - subscriber.last_notification_opened).days > 730:
issues["inactive_long_term"].append(subscriber.id)
return issues
def send_webhook_failure_notification(subscriber):
"""
Email subscriber when their webhook is consistently failing.
"""
# Send email to subscriber asking them to update webhook URL
send_email(
to=subscriber.notification_email,
subject="Action Required: Your status page webhook is failing",
body=f"""
Your webhook URL is not receiving our status notifications.
Webhook URL: {subscriber.webhook_url}
Last successful delivery: {subscriber.last_webhook_success}
Failures in last 7 days: {subscriber.webhook_failure_streak_days}
Please update your webhook URL at {subscriber.preferences_url}.
If no action is taken in 14 days, we'll disable your webhook
subscription to avoid repeated failures.
"""
)
Measuring Notification Effectiveness
Track whether your notifications are actually reaching and helping subscribers:
def calculate_notification_metrics(notifications, days=30):
"""
Track notification delivery and engagement.
"""
cutoff = datetime.utcnow() - timedelta(days=days)
recent = [n for n in notifications if n.sent_at >= cutoff]
return {
"total_sent": len(recent),
"email_delivery_rate": calculate_email_delivery_rate(recent),
"webhook_delivery_rate": calculate_webhook_delivery_rate(recent),
"email_open_rate": calculate_open_rate(recent), # Track via pixel or click
"unsubscribe_rate": calculate_unsubscribe_rate(recent),
"subscriber_growth": calculate_subscriber_growth(days),
# Time-to-notification (from incident to first subscriber notified)
"avg_notification_delay_seconds": calculate_avg_delay(recent)
}
Conclusion
Subscriber notifications turn your status page from a passive reference into an active communication channel. Customers who receive well-timed, appropriately detailed notifications during incidents are measurably more forgiving of the outage itself — because they know they're being kept informed rather than left to wonder. The key is calibrating notification timing and detail to be genuinely helpful: notify when there's confirmed impact, update when status meaningfully changes, resolve when restored. AzMonitor's status page includes subscriber management with email and webhook notification support, letting you provide this communication layer without building it from scratch.
3 monitors free forever · No credit card needed · Set up in 2 minutes
Start monitoring free →