Nova Uptime
Gidsenincident-responseescalationon-call

Custom e-mailalerts en escalaties: geavanceerde incident-routing

Ontwerp escalatie-workflows die de juiste persoon op het juiste moment paginen. Gids voor alert-routing, on-call integratie en escalatiebeleid.

SN
Sumit Nova Uptime
28 februari 2026 · 8 min read
Share:

Het escalatieprobleem#

Het is 3 uur 's nachts. Je website gaat down. Iemand moet meteen reageren.

Zonder escalatie:

  • Alert gaat naar het on-call Slack-kanaal
  • Niemand ziet het (iedereen slaapt, Slack-notificaties uit)
  • 45 minuten later wekken klantklachten iemand
  • MTTR: 45 minuten

Met slimme escalatie:

  • Alert gaat naar Slack (lage prioriteit)
  • Geen acknowledgment binnen 2 minuten → SMS naar primaire on-call
  • Geen reactie binnen 5 minuten → Bel de on-call backup
  • MTTR: 5 minuten

Deze gids laat zien hoe je intelligente escalatie-workflows bouwt.

Incident-severity begrijpen#

Classificeer incidents eerst op severity:

Tier 1: Critical (direct paginen)#

  • Productie-website down (raakt omzet)
  • Payment gateway-failure
  • API geeft 5xx-errors
  • Database-replicatie down

Acties:

  • Slack #critical-incidents (altijd in de gaten gehouden)
  • SMS naar primaire on-call
  • Telefoontje als er geen SMS-reactie komt
  • Automatische Jira-ticket-aanmaak
  • Statuspagina-update

Tier 2: Warning (paginen tijdens kantooruren)#

  • Verslechterde response time
  • Niet-kritieke service-errors
  • E-maildeliverability-issues
  • Trage database-queries

Acties:

  • Slack #alerts (gecheckt tijdens kantooruren)
  • Jira-ticket-aanmaak
  • E-maildigest aan einde van de dag

Tier 3: Info (alleen loggen)#

  • Domein verloopt over 30 dagen
  • SSL verloopt over 90 dagen
  • Wekelijks trendrapport
  • Niet-kritieke metric-threshold

Acties:

  • Wekelijkse e-maildigest
  • Dashboard-notificatie (geen page)

Je escalatiebeleid bouwen#

Stap 1: Definieer on-call rotaties#

Maak een rotatie die laat zien wie wanneer on-call is:

Maandag-vrijdag 9:00 - 17:00: Alice (primair), Bob (backup)
Maandag-vrijdag 17:00 - 9:00: Charlie (primair), Diana (backup)
Zaterdag-zondag 24 uur: Eve (primair), Frank (backup)
Feestdagen: George (de hele dag on-call)

Stap 2: Definieer escalatietijden#

T+0:    Alert gaat naar Slack
T+2min: Geen acknowledgment → SMS naar primaire
T+5min: Geen reactie → Bel primaire
T+10min: Geen reactie → SMS naar backup
T+15min: Nog steeds geen reactie → Het hele team paginen

Stap 3: Implementeer de escalatielogica#

In Nova Uptime (indien ondersteund):

  1. Domein-instellingen → Alerting
  2. Severity instellen: Critical
  3. Escalatie configureren:
    • Stap 1: Slack #critical-incidents
    • Stap 2 (2 min): SMS naar on-call
    • Stap 3 (5 min): Telefoontje
    • Stap 4 (10 min): Hele team

Via webhook + custom systeem:

async function handleCriticalIncident({ domain, detectedAt }) {
  const oncall = await getOnCallEngineer(new Date());

  // Step 1: Immediate Slack alert
  const slackMessage = await postToSlack({
    channel: '#critical-incidents',
    text: `🚨 CRITICAL: ${domain} is down`,
    blocks: [
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `@${oncall.slackHandle}: ${domain} is down. Acknowledge with reaction.`
        }
      }
    ]
  });

  // Step 2: Wait 2 minutes for acknowledgment
  const acknowledged = await waitForAcknowledgment(slackMessage, 2 * 60 * 1000);

  if (!acknowledged) {
    // Step 2: Send SMS
    await sendSMS({
      to: oncall.phone,
      message: `CRITICAL: ${domain} down. Reply OK to acknowledge.`
    });
  }

  // Step 3: Wait 5 minutes total
  const smsAcknowledged = await waitForSMS(oncall.phone, 5 * 60 * 1000);

  if (!smsAcknowledged) {
    // Step 3: Phone call
    await makePhoneCall({
      to: oncall.phone,
      message: `Critical incident. Website ${domain} is down. Press 1 to acknowledge.`
    });
  }

  // ... Continue escalation chain
}

Geavanceerd: smart alert routing#

Patroon 1: Routeer op tijd van de dag#

Verschillende mensen on-call op verschillende momenten.

async function getOnCallEngineer(timestamp) {
  const hour = new Date(timestamp).getHours();
  const dayOfWeek = new Date(timestamp).getDay();

  // Business hours (9 AM - 5 PM weekdays)
  if (dayOfWeek >= 1 && dayOfWeek <= 5 && hour >= 9 && hour < 17) {
    return {
      name: 'Alice',
      slackHandle: 'alice',
      phone: '+1-555-0100',
      email: 'alice@company.com'
    };
  }

  // After hours (weekdays)
  if (dayOfWeek >= 1 && dayOfWeek <= 5 && (hour < 9 || hour >= 17)) {
    return {
      name: 'Charlie',
      slackHandle: 'charlie',
      phone: '+1-555-0102',
      email: 'charlie@company.com'
    };
  }

  // Weekend
  if (dayOfWeek === 0 || dayOfWeek === 6) {
    return {
      name: 'Eve',
      slackHandle: 'eve',
      phone: '+1-555-0104',
      email: 'eve@company.com'
    };
  }
}

Patroon 2: Routeer op domein#

Verschillende teams zijn eigenaar van verschillende domeinen.

async function getTeamForDomain(domain) {
  // Engineering team owns api.*, backend.*
  if (domain.startsWith('api.') || domain.startsWith('backend.')) {
    return 'engineering';
  }

  // Infrastructure team owns server, monitoring, infra
  if (domain.includes('server') || domain.includes('monitor')) {
    return 'infrastructure';
  }

  // Support team owns customer-facing domains
  if (domain.startsWith('support.') || domain.startsWith('customer.')) {
    return 'support';
  }

  // Default: DevOps
  return 'devops';
}

async function handleIncident({ domain, severity }) {
  const team = await getTeamForDomain(domain);
  const oncall = await getOnCallEngineer(team, new Date());

  if (severity === 'critical') {
    await escalate(oncall);
  }
}

Patroon 3: Routeer op incident-type#

Verschillende expertise voor verschillende failures.

async function getExpertForIncident(domain, incidentType) {
  if (incidentType === 'database_down') {
    // Page database expert
    return await getOnCallExpert('database');
  } else if (incidentType === 'api_errors') {
    // Page API lead
    return await getOnCallExpert('backend');
  } else if (incidentType === 'email_delivery_failing') {
    // Page email ops
    return await getOnCallExpert('email');
  } else if (incidentType === 'ssl_expired') {
    // Page security
    return await getOnCallExpert('security');
  }

  // Default: on-call engineer
  return await getOnCallEngineer(new Date());
}

Patroon 4: Voorwaardelijke escalatie#

Verschillende escalatiepaden op basis van incident-eigenschappen.

async function escalateIncident({ domain, severity, duration }) {
  const oncall = await getOnCallEngineer(new Date());

  if (severity === 'critical' && duration > 5 * 60 * 1000) {
    // Critical for >5 minutes: Aggressive escalation
    await Promise.all([
      postSlack({ channel: '#critical-incidents', text: 'CRITICAL ESCALATION' }),
      sendSMS(oncall.phone),
      makePhoneCall(oncall.phone),
      pageBackup(oncall.backup)
    ]);
  } else if (severity === 'critical') {
    // Critical but recent: Gentle escalation
    await Promise.all([
      postSlack({ channel: '#critical-incidents' }),
      sendSMS(oncall.phone)
    ]);
  } else if (severity === 'warning') {
    // Just log
    await postSlack({ channel: '#alerts' });
  }
}

Integratie met PagerDuty#

Voor geavanceerd on-call beheer integreer je met PagerDuty:

const PagerDutyClient = require('pagerduty');

async function pageOnCallVia PagerDuty(domain, severity) {
  const client = new PagerDutyClient({
    token: process.env.PAGERDUTY_TOKEN
  });

  // Create incident
  const incident = await client.incidents.create({
    type: 'incident_reference',
    incident: {
      type: 'incident',
      title: `${domain} is down`,
      body: {
        type: 'incident_body',
        description: `Website ${domain} is down. Response: Down. Severity: ${severity}`
      },
      urgency: severity === 'critical' ? 'high' : 'low',
      service: {
        id: process.env.PAGERDUTY_SERVICE_ID,
        type: 'service_reference'
      }
    }
  });

  console.log(`Created PagerDuty incident: ${incident.id}`);
}

Acknowledgment en handoff#

Acknowledgment-patronen#

De on-call engineer moet het incident bevestigen:

// Via Slack reaction
async function waitForSlackAcknowledgment(messageId, maxWait) {
  const startTime = Date.now();

  while (Date.now() - startTime < maxWait) {
    // Poll for reactions
    const reactions = await getSlackMessageReactions(messageId);

    if (reactions.includes('white_check_mark')) {
      return true; // Acknowledged
    }

    await sleep(10 * 1000); // Check every 10 seconds
  }

  return false; // Not acknowledged within max wait
}

// Via SMS
async function waitForSmsAcknowledgment(phone, maxWait) {
  const startTime = Date.now();

  while (Date.now() - startTime < maxWait) {
    // Poll SMS responses
    const responses = await getSmsResponses(phone);

    if (responses.some(m => m.text.toUpperCase().includes('OK'))) {
      return true; // Acknowledged
    }

    await sleep(5 * 1000); // Check every 5 seconds
  }

  return false; // Not acknowledged
}

Handoff tussen teams#

Als de ene on-call engineer moet overdragen aan een andere:

async function handoffIncident(incident, fromEngineer, toEngineer) {
  // Update incident
  incident.assignedTo = toEngineer;
  incident.handoffAt = new Date();
  await incident.save();

  // Notify both
  await sendMessage({
    to: fromEngineer.slack,
    text: `Handing off ${incident.domain} to ${toEngineer.name}`
  });

  await sendMessage({
    to: toEngineer.slack,
    text: `Taking over incident: ${incident.domain}. See: ${incident.dashboard}`
  });

  // Update status page
  await updateStatusPage({
    message: `Working with ${toEngineer.name}'s team on investigation`
  });
}

Effectiviteit van escalatie meten#

Track escalatie-metrics:

async function analyzeEscalationMetrics() {
  const incidents = await Incident.find({
    createdAfter: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) // Last 30 days
  });

  const metrics = {
    totalIncidents: incidents.length,
    avgTimeToAcknowledgment: calculateAvg(
      incidents.map(i => i.acknowledgedAt - i.alertedAt)
    ),
    avgTimeToEscalation: calculateAvg(
      incidents.map(i => i.escalatedAt - i.alertedAt)
    ),
    escalationRate: incidents.filter(i => i.escalatedAt).length / incidents.length,
    avgMTTR: calculateAvg(
      incidents.map(i => i.resolvedAt - i.alertedAt)
    )
  };

  console.log('Escalation Metrics (Last 30 days):');
  console.log(`Total Incidents: ${metrics.totalIncidents}`);
  console.log(`Avg Time to Acknowledgment: ${metrics.avgTimeToAcknowledgment / 60}m`);
  console.log(`Escalation Rate: ${(metrics.escalationRate * 100).toFixed(1)}%`);
  console.log(`Avg MTTR: ${metrics.avgMTTR / 60}m`);
}

Veelgemaakte escalatiefouten#

Fout 1: Alert fatigue leidt tot wegklikken#

Probleem: Te veel alerts → team reageert niet meer → echte issues gemist

Oplossing: Gebruik strikte thresholds. Page alleen voor echt kritieke issues.

Fout 2: Te agressief escaleren#

Probleem: Het hele team paginen bij elk incident → burn-out → mensen vertrekken

Oplossing: Escaleer geleidelijk. Geef de primaire 5 minuten voordat je de backup paginet.

Fout 3: Te traag escaleren#

Probleem: Critical incident blijft 30 minuten onopgemerkt → enorme impact

Oplossing: Voor critical incidents, escaleer binnen 2-5 minuten.

Fout 4: Geen handoff-proces#

Probleem: Primaire on-call wist niet dat er werd overgedragen → chaos

Oplossing: Communiceer handoffs expliciet via Slack/e-mail.

Samenvatting: escalatie-setup checklist#

  • ✅ Definieer incident-severity tiers (Critical/Warning/Info)
  • ✅ Maak een on-call rotatieschema
  • ✅ Definieer escalatie-timing (2 min → 5 min → 10 min → iedereen)
  • ✅ Zet escalatiekanalen op (Slack → SMS → Telefoon → hele team)
  • ✅ Configureer routing op tijd van de dag
  • ✅ Routeer op domein/team-eigenaarschap
  • ✅ Routeer op incident-type (database vs API vs e-mail)
  • ✅ Integreer met PagerDuty (indien van toepassing)
  • ✅ Zet acknowledgment-mechanismen op (Slack-reactie, SMS-reactie)
  • ✅ Definieer handoff-procedures
  • ✅ Track escalatie-metrics
  • ✅ Maandelijkse review van escalatie-effectiviteit

Begin vandaag#

Begin simpel: alleen Slack + SMS. Voeg complexiteit toe (PagerDuty, conditional routing) naarmate je incident-volume groeit.

Documenteer je escalatiebeleid in je team-wiki. Deel het met alle teamleden. Test elk kwartaal of alles nog werkt.

Je escalatiebeleid is het verschil tussen "incident opgelost in 5 minuten" en "storing duurde 3 uur." Investeer in het goed neerzetten hiervan.

Monitor Your Website Before It Goes Down

Get uptime monitoring, SSL tracking, domain expiry alerts, and email health checks. Free plan — no credit card required.

Start Monitoring Free

Gerelateerde artikelen