Source: https://cli.nylas.com/guides/powershell-email-monitoring

Guide

# Server Monitoring Alerts with PowerShell Email

Gartner reports the average cost of IT downtime is $5,600 per minute. You don't need Datadog or PagerDuty for basic server monitoring. PowerShell can check disk space, service health, Event Log errors, and scheduled task status -- then email you the moment something breaks. This guide covers each pattern with Nylas CLI. Works across all major email providers.

Written by [Qasim Muhammad](https://cli.nylas.com/authors/qasim-muhammad) • Staff SRE

Reviewed by [Nick Barraclough](https://cli.nylas.com/authors/nick-barraclough)

Updated April 11, 2026

Verified

 —

CLI

3.1.1

 ·

Gmail, Outlook

 ·

last tested

April 11, 2026

> **TL;DR:** Use PowerShell cmdlets like `Get-PSDrive`, `Get-Service`, and `Get-WinEvent` to check server health. When something's wrong, send an alert with `nylas email send`. Schedule the scripts with Task Scheduler to run every 5-60 minutes.

## One-time setup

These monitoring scripts assume you already have the CLI installed and authenticated. If not, the [getting started guide](https://cli.nylas.com/guides/getting-started) covers all install methods. The alert emails go out through your authenticated account, so pick one your team monitors.

## Disk space alerts

According to Microsoft's Windows Server documentation, running below 10% free disk space causes performance degradation, and below 1% can corrupt the Event Log. This script checks every drive and alerts when free space drops below a threshold.

```powershell
# disk-alert.ps1 -- Email alert when disk space is low
param(
    [int]$ThresholdPercent = 10,
    [string]$AlertTo = "ops@company.com"
)

$drives = Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" |
    Select-Object DeviceID,
        @{N='SizeGB'; E={[math]::Round($_.Size/1GB, 1)}},
        @{N='FreeGB'; E={[math]::Round($_.FreeSpace/1GB, 1)}},
        @{N='FreePct'; E={[math]::Round(($_.FreeSpace/$_.Size)*100, 1)}}

$lowDrives = $drives | Where-Object { $_.FreePct -lt $ThresholdPercent }

if ($lowDrives) {
    $hostname = $env:COMPUTERNAME
    $details = $lowDrives | ForEach-Object {
        "$($_.DeviceID) $($_.FreeGB) GB free of $($_.SizeGB) GB ($($_.FreePct)%)"
    }

    $body = @"
Disk space alert on $hostname

The following drives are below $ThresholdPercent% free:

$($details -join "`n")

All drives:
$($drives | ForEach-Object { "$($_.DeviceID) $($_.FreeGB)/$($_.SizeGB) GB ($($_.FreePct)%)" } | Out-String)

Checked at $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
"@

    nylas email send --to $AlertTo `
        --subject "DISK ALERT: $hostname - $($lowDrives.Count) drive(s) low" `
        --body $body --yes

    Write-Host "Alert sent for $($lowDrives.Count) low drive(s)"
} else {
    Write-Host "All drives OK (above $ThresholdPercent% free)"
}
```

## Service health checks

Windows servers run dozens of services. If SQL Server, IIS, or your application service stops, you want to know immediately -- not when a customer reports it 20 minutes later.

```powershell
# service-monitor.ps1 -- Check critical services and alert on failures
param(
    [string[]]$CriticalServices = @(
        "W3SVC",           # IIS
        "MSSQLSERVER",     # SQL Server
        "wuauserv",        # Windows Update
        "Spooler",         # Print Spooler
        "WinRM"            # Windows Remote Management
    ),
    [string]$AlertTo = "ops@company.com",
    [switch]$AttemptRestart
)

$hostname = $env:COMPUTERNAME
$stoppedServices = @()

foreach ($svcName in $CriticalServices) {
    $svc = Get-Service -Name $svcName -ErrorAction SilentlyContinue

    if (-not $svc) {
        Write-Host "Service not found: $svcName" -ForegroundColor DarkGray
        continue
    }

    if ($svc.Status -ne 'Running') {
        Write-Host "DOWN: $svcName ($($svc.Status))" -ForegroundColor Red

        if ($AttemptRestart) {
            Write-Host "  Attempting restart..."
            try {
                Restart-Service -Name $svcName -Force -ErrorAction Stop
                Write-Host "  Restarted successfully" -ForegroundColor Green
                $stoppedServices += "$svcName - was $($svc.Status), RESTARTED"
            } catch {
                Write-Host "  Restart failed: $_" -ForegroundColor Red
                $stoppedServices += "$svcName - $($svc.Status), restart FAILED: $_"
            }
        } else {
            $stoppedServices += "$svcName - $($svc.Status)"
        }
    } else {
        Write-Host "OK: $svcName" -ForegroundColor Green
    }
}

if ($stoppedServices.Count -gt 0) {
    $body = @"
Service alert on $hostname

$($stoppedServices.Count) critical service(s) not running:

$($stoppedServices | ForEach-Object { "  - $_" } | Out-String)

Full service status:
$($CriticalServices | ForEach-Object {
    $s = Get-Service -Name $_ -ErrorAction SilentlyContinue
    if ($s) { "  $($_.PadRight(20)) $($s.Status)" } else { "  $($_.PadRight(20)) NOT FOUND" }
} | Out-String)

Checked at $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
"@

    nylas email send --to $AlertTo `
        --subject "SERVICE ALERT: $hostname - $($stoppedServices.Count) service(s) down" `
        --body $body --yes
}
```

## Windows Event Log alerts

The Windows Event Log captures errors, warnings, and critical events from the OS and applications. According to Microsoft, Event ID 6008 indicates an unexpected shutdown, and Event IDs 1000-1001 in the Application log signal application crashes. This script queries recent errors and emails a summary.

```powershell
# eventlog-alert.ps1 -- Email recent Error and Critical events
param(
    [int]$HoursBack = 1,
    [string]$AlertTo = "ops@company.com"
)

$hostname = $env:COMPUTERNAME
$startTime = (Get-Date).AddHours(-$HoursBack)

# Query System and Application logs for Error and Critical events
$events = @()
foreach ($logName in @('System', 'Application')) {
    $events += Get-WinEvent -FilterHashtable @{
        LogName   = $logName
        Level     = 1, 2  # 1=Critical, 2=Error
        StartTime = $startTime
    } -ErrorAction SilentlyContinue
}

if ($events.Count -eq 0) {
    Write-Host "No errors in the last $HoursBack hour(s)"
    exit 0
}

# Group by source for a summary
$grouped = $events | Group-Object ProviderName |
    Sort-Object Count -Descending

$summary = $grouped | ForEach-Object {
    "$($_.Count) event(s) from $($_.Name)"
}

# Get the 5 most recent events with details
$recent = $events | Sort-Object TimeCreated -Descending |
    Select-Object -First 5 |
    ForEach-Object {
        @"
  [$($_.TimeCreated.ToString('HH:mm:ss'))] $($_.ProviderName)
  Event ID: $($_.Id) | Level: $($_.LevelDisplayName)
  $($_.Message.Substring(0, [Math]::Min(200, $_.Message.Length)))
"@
    }

$body = @"
Event Log alert on $hostname
$($events.Count) Error/Critical events in the last $HoursBack hour(s)

Summary by source:
$($summary | ForEach-Object { "  $_" } | Out-String)

Recent events:
$($recent -join "`n`n")

Checked at $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
"@

nylas email send --to $AlertTo `
    --subject "EVENT LOG: $hostname - $($events.Count) error(s) in last $($HoursBack)h" `
    --body $body --yes

Write-Host "Alert sent: $($events.Count) events found"
```

## Scheduled task failure monitoring

Windows Task Scheduler tasks can fail silently. This script checks the last run result of critical tasks and alerts on failures.

```powershell
# task-monitor.ps1 -- Alert on failed scheduled tasks
param(
    [string[]]$TaskNames = @(
        "NylasInboxReport",
        "DatabaseBackup",
        "LogCleanup"
    ),
    [string]$AlertTo = "ops@company.com"
)

$hostname = $env:COMPUTERNAME
$failedTasks = @()

foreach ($taskName in $TaskNames) {
    $task = Get-ScheduledTaskInfo -TaskName $taskName -ErrorAction SilentlyContinue

    if (-not $task) {
        $failedTasks += "$taskName - TASK NOT FOUND"
        continue
    }

    # LastTaskResult 0 = success, anything else = failure
    if ($task.LastTaskResult -ne 0) {
        $hexResult = "0x{0:X}" -f $task.LastTaskResult
        $lastRun = $task.LastRunTime.ToString('yyyy-MM-dd HH:mm:ss')
        $failedTasks += "$taskName - Exit code $hexResult (last run: $lastRun)"
    }
}

if ($failedTasks.Count -gt 0) {
    $body = @"
Scheduled task alert on $hostname

$($failedTasks.Count) task(s) failed:

$($failedTasks | ForEach-Object { "  - $_" } | Out-String)

Checked at $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
"@

    nylas email send --to $AlertTo `
        --subject "TASK ALERT: $hostname - $($failedTasks.Count) task(s) failed" `
        --body $body --yes
} else {
    Write-Host "All $($TaskNames.Count) tasks passed last run"
}
```

## CPU and memory threshold alerts

Sustained high CPU or memory usage for extended periods usually signals a runaway process or memory leak. This script checks both and includes the top processes by resource usage in the alert.

```powershell
# resource-alert.ps1 -- Alert on high CPU or memory usage
param(
    [int]$CpuThreshold = 90,
    [int]$MemoryThreshold = 90,
    [string]$AlertTo = "ops@company.com"
)

$hostname = $env:COMPUTERNAME
$alerts = @()

# CPU check (average over 5 samples, 2s apart)
$cpuAvg = (Get-Counter 'Processor(_Total)% Processor Time' `
    -SampleInterval 2 -MaxSamples 5 |
    ForEach-Object { $_.CounterSamples.CookedValue } |
    Measure-Object -Average).Average
$cpuPct = [math]::Round($cpuAvg, 1)

if ($cpuPct -ge $CpuThreshold) {
    $topCpu = Get-Process | Sort-Object CPU -Descending |
        Select-Object -First 5 Name, @{N='CPU_Sec';E={[math]::Round($_.CPU, 1)}}, Id
    $alerts += "CPU at $cpuPct% (threshold: $CpuThreshold%)"
    $alerts += "Top processes by CPU:"
    $topCpu | ForEach-Object { $alerts += "  $($_.Name) (PID $($_.Id)): $($_.CPU_Sec)s" }
}

# Memory check
$os = Get-CimInstance Win32_OperatingSystem
$totalMB = [math]::Round($os.TotalVisibleMemorySize / 1024)
$freeMB = [math]::Round($os.FreePhysicalMemory / 1024)
$usedPct = [math]::Round((1 - $freeMB / $totalMB) * 100, 1)

if ($usedPct -ge $MemoryThreshold) {
    $topMem = Get-Process | Sort-Object WorkingSet64 -Descending |
        Select-Object -First 5 Name, @{N='MemMB';E={[math]::Round($_.WorkingSet64/1MB)}}, Id
    $alerts += "Memory at $usedPct% ($freeMB MB free of $totalMB MB)"
    $alerts += "Top processes by memory:"
    $topMem | ForEach-Object { $alerts += "  $($_.Name) (PID $($_.Id)): $($_.MemMB) MB" }
}

if ($alerts.Count -gt 0) {
    $body = @"
Resource alert on $hostname

$($alerts -join "`n")

Checked at $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
"@
    nylas email send --to $AlertTo `
        --subject "RESOURCE ALERT: $hostname - CPU:$cpuPct% MEM:$usedPct%" `
        --body $body --yes
} else {
    Write-Host "Resources OK: CPU $cpuPct%, Memory $usedPct%"
}
```

## SSL certificate expiry alerts

According to the Netcraft Web Server Survey, 2.4% of HTTPS sites serve expired certificates at any given time. Catch yours before it expires.

```powershell
# ssl-expiry-alert.ps1 -- Alert on certificates expiring within N days
param(
    [string[]]$Domains = @("app.company.com", "api.company.com"),
    [int]$WarningDays = 30,
    [string]$AlertTo = "ops@company.com"
)

$expiring = @()

foreach ($domain in $Domains) {
    try {
        $req = [System.Net.HttpWebRequest]::Create("https://$domain")
        $req.Timeout = 10000
        $req.AllowAutoRedirect = $false
        $resp = $req.GetResponse()
        $cert = $req.ServicePoint.Certificate
        $expiry = [DateTime]::Parse($cert.GetExpirationDateString())
        $daysLeft = ($expiry - (Get-Date)).Days
        $resp.Close()

        if ($daysLeft -le $WarningDays) {
            $expiring += "$domain expires $($expiry.ToString('yyyy-MM-dd')) ($daysLeft days)"
        }
        Write-Host "$domain - expires in $daysLeft days"
    } catch {
        $expiring += "$domain - FAILED TO CHECK: $_"
    }
}

if ($expiring.Count -gt 0) {
    $body = "SSL certificate expiry warning:`n`n$($expiring -join "`n")"
    nylas email send --to $AlertTo `
        --subject "SSL EXPIRY: $($expiring.Count) cert(s) within $WarningDays days" `
        --body $body --yes
}
```

## Schedule all monitors with Task Scheduler

Register each monitoring script as a scheduled task. Run disk and service checks every 15 minutes, Event Log checks hourly, and SSL checks daily.

```powershell
# Register monitoring scripts as scheduled tasks
$monitors = @(
    @{ Name = "DiskAlert"; Script = "disk-alert.ps1"; Interval = 15 }
    @{ Name = "ServiceMonitor"; Script = "service-monitor.ps1"; Interval = 15 }
    @{ Name = "EventLogAlert"; Script = "eventlog-alert.ps1"; Interval = 60 }
    @{ Name = "ResourceAlert"; Script = "resource-alert.ps1"; Interval = 5 }
    @{ Name = "TaskMonitor"; Script = "task-monitor.ps1"; Interval = 60 }
    @{ Name = "SSLExpiry"; Script = "ssl-expiry-alert.ps1"; Interval = 1440 }
)

foreach ($m in $monitors) {
    $action = New-ScheduledTaskAction `
        -Execute "pwsh.exe" `
        -Argument "-NoProfile -File C:Monitoring$($m.Script)"

    $trigger = New-ScheduledTaskTrigger `
        -Once -At (Get-Date) `
        -RepetitionInterval (New-TimeSpan -Minutes $m.Interval) `
        -RepetitionDuration (New-TimeSpan -Days 365)

    Register-ScheduledTask `
        -TaskName "Nylas-$($m.Name)" `
        -Action $action `
        -Trigger $trigger `
        -Description "$($m.Name) - runs every $($m.Interval) min"

    Write-Host "Registered: Nylas-$($m.Name) (every $($m.Interval) min)"
}
```

## Frequently asked questions

### How do I send disk space alerts from PowerShell?

Use `Get-CimInstance Win32_LogicalDisk` to check free space on each drive. Compare against a threshold (e.g., 10%). When a drive drops below, send an alert with `nylas email send`. Schedule the script with Task Scheduler to run every 15 minutes.

### Can I monitor Windows services and get email alerts when they stop?

Yes. Use `Get-Service` to check status of critical services. When any service isn't running, the script can attempt a restart with `Restart-Service` and then email you the result. Add `-AttemptRestart` to enable auto-restart before alerting.

### How do I email Windows Event Log errors from PowerShell?

Use `Get-WinEvent -FilterHashtable` to query Error and Critical events from the last hour. Group by source, format the top 5 events with details, and send via `nylas email send`. Schedule hourly to catch issues as they happen.

---

## Next steps

- [Automated email reports with PowerShell](https://cli.nylas.com/guides/powershell-email-reports) -- CSV attachments, HTML tables, weekly digests
- [CI/CD email notifications](https://cli.nylas.com/guides/powershell-email-cicd) -- build alerts, deployment reports from pipelines
- [Send email from PowerShell](https://cli.nylas.com/guides/send-email-powershell) -- the foundation guide for PowerShell email
- [Full command reference](https://cli.nylas.com/docs/commands) -- every flag and subcommand
