Status Pages

Subscriber Notifications: Keeping Customers Informed During Incidents

Learn how to set up and manage status page subscriber notifications — email alerts, webhooks, SMS, and best practices for notifying customers at the right time with the right information.

AzMonitor TeamAugust 20, 20257 min read · 1,305 wordsUpdated January 20, 2026
subscriber notificationsstatus pageincident notificationscustomer communication

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.

Tags:subscriber notificationsstatus pageincident notificationscustomer communication
Back to blog
A
AzMonitor Team
The AzMonitor team writes guides based on experience monitoring millions of endpoints daily across 10,000+ customer environments. Our expertise covers uptime monitoring, SRE practices, and reliability engineering.
Try AzMonitor free

3 monitors free forever · No credit card needed · Set up in 2 minutes

Start monitoring free →