PowerShell ile E-posta Gönderme: Send-MailMessage Kullanım Kılavuzu

Sistem yöneticilerinin en sık ihtiyaç duyduğu şeylerden biri otomatik bildirimler ve raporlardır. Bir backup tamamlandığında, bir servis çöktüğünde ya da disk dolmaya başladığında anında haberdar olmak istersiniz. PowerShell’in Send-MailMessage cmdlet’i tam da bu noktada devreye girer. Script’lerinize birkaç satır ekleyerek e-posta gönderme yeteneği kazandırabilir, gecenin üçünde telefona bakmak zorunda kalmadan sisteminizin durumunu takip edebilirsiniz.

Send-MailMessage Nedir ve Neden Kullanılır?

Send-MailMessage, PowerShell’in yerleşik e-posta gönderme cmdlet’idir. Windows PowerShell 2.0 ile birlikte geldi ve bugün hala Windows ortamlarında en yaygın kullanılan bildirim araçlarından biri olmaya devam ediyor. .NET Framework’ün System.Net.Mail namespace’ini kullandığı için kurumsal ortamlardaki SMTP sunucularıyla sorunsuz çalışır.

Resmi Microsoft belgelerinde Send-MailMessage cmdlet’inin “obsolete” (kullanımdan kaldırılmış) olarak işaretlendiğini görebilirsiniz. Bu durum bazı karmaşaya yol açıyor. Microsoft bu işaretlemeyi güvenlik açıkları nedeniyle değil, cmdlet’in RFC standartlarının tamamını desteklememesi ve modern kimlik doğrulama yöntemlerini (OAuth2 gibi) içermemesi nedeniyle yaptı. Kurumsal bir Exchange ortamında ya da iç ağdaki SMTP relay’inde kullanmak için hala tamamen işlevsel ve güvenilirdir. Modern bulut servisleri (Gmail, Office 365) için ise aşağıda alternatif yöntemlere de değineceğiz.

Temel Parametreler

Send-MailMessage cmdlet’inin bilmeniz gereken parametreleri şunlardır:

  • -To: Alıcı e-posta adresi veya adresleri
  • -From: Gönderen e-posta adresi
  • -Subject: E-posta konusu
  • -Body: E-posta içeriği
  • -SmtpServer: Kullanılacak SMTP sunucusu
  • -Port: SMTP port numarası (varsayılan 25)
  • -Credential: SMTP kimlik doğrulaması için kullanıcı adı/şifre
  • -UseSsl: SSL/TLS bağlantısı kullanmak için
  • -Attachments: Eklenecek dosya yolu veya yolları
  • -Cc: Karbon kopya alıcıları
  • -Bcc: Gizli kopya alıcıları
  • -BodyAsHtml: Body içeriğini HTML olarak yorumla
  • -Priority: E-posta önceliği (Normal, High, Low)
  • -Encoding: Karakter kodlaması (UTF8, ASCII, vb.)

İlk Adım: Basit Bir Test E-postası

Ortamınızı test etmek için en sade haliyle başlayalım:

Send-MailMessage `
    -To "[email protected]" `
    -From "[email protected]" `
    -Subject "Test Mesaji" `
    -Body "Bu bir test e-postasıdır." `
    -SmtpServer "mail.sirketiniz.com"

Eğer SMTP sunucunuz kimlik doğrulaması gerektiriyorsa:

$credential = Get-Credential
Send-MailMessage `
    -To "[email protected]" `
    -From "[email protected]" `
    -Subject "Test Mesaji" `
    -Body "Bu bir test e-postasıdır." `
    -SmtpServer "mail.sirketiniz.com" `
    -Port 587 `
    -UseSsl `
    -Credential $credential

Get-Credential çalıştırdığınızda bir pencere açılır ve kullanıcı adı/şifre ister. Script’leri otomatize etmek istediğinizde bunu kullanmak pratik değildir, credential saklama konusuna ilerleyen bölümlerde değineceğiz.

Gerçek Dünya Senaryosu 1: Disk Doluluk Uyarısı

Disk kullanımını izleyip belirli bir eşiği aştığında e-posta gönderen basit ama son derece işe yarar bir script:

$threshold = 85
$smtpServer = "mail.sirketiniz.com"
$from = "[email protected]"
$to = "[email protected]"

Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -gt 0 } | ForEach-Object {
    $drive = $_
    $usedPercent = [math]::Round(($drive.Used / ($drive.Used + $drive.Free)) * 100, 2)
    
    if ($usedPercent -ge $threshold) {
        $usedGB = [math]::Round($drive.Used / 1GB, 2)
        $freeGB = [math]::Round($drive.Free / 1GB, 2)
        
        $subject = "[UYARI] $($env:COMPUTERNAME) - $($drive.Name): Diski %$usedPercent Dolu"
        $body = @"
Sunucu: $($env:COMPUTERNAME)
Disk: $($drive.Name):
Kullanim: %$usedPercent
Kullanilan Alan: $usedGB GB
Bos Alan: $freeGB GB
Tarih: $(Get-Date -Format "dd.MM.yyyy HH:mm:ss")

Lutfen gerekli aksiyonu aliniz.
"@
        
        Send-MailMessage `
            -To $to `
            -From $from `
            -Subject $subject `
            -Body $body `
            -SmtpServer $smtpServer `
            -Encoding UTF8
        
        Write-Host "Uyari e-postasi gonderildi: $($drive.Name): diski %$usedPercent dolu"
    }
}

Bu script’i Task Scheduler’a alarak her saat çalıştırabilirsiniz. Böylece disk dolmadan önce haberdar olursunuz.

HTML Formatında E-posta Gönderme

Düz metin yerine HTML formatında profesyonel görünümlü e-postalar göndermek için -BodyAsHtml parametresini kullanırsınız:

$htmlBody = @"
<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: Arial, sans-serif; font-size: 14px; }
        table { border-collapse: collapse; width: 100%; }
        th { background-color: #4472C4; color: white; padding: 8px; text-align: left; }
        td { padding: 8px; border: 1px solid #ddd; }
        tr:nth-child(even) { background-color: #f2f2f2; }
        .critical { color: red; font-weight: bold; }
        .warning { color: orange; font-weight: bold; }
        .ok { color: green; }
    </style>
</head>
<body>
    <h2>Sunucu Durum Raporu - $(Get-Date -Format "dd.MM.yyyy HH:mm")</h2>
    <p>Sunucu: <strong>$($env:COMPUTERNAME)</strong></p>
    <table>
        <tr>
            <th>Disk</th>
            <th>Toplam</th>
            <th>Kullanilan</th>
            <th>Bos</th>
            <th>Doluluk</th>
        </tr>
"@

Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -gt 0 } | ForEach-Object {
    $pct = [math]::Round(($_.Used / ($_.Used + $_.Free)) * 100, 1)
    $cssClass = if ($pct -ge 90) { "critical" } elseif ($pct -ge 75) { "warning" } else { "ok" }
    
    $htmlBody += @"
        <tr>
            <td>$($_.Name):</td>
            <td>$([math]::Round(($_.Used + $_.Free)/1GB, 1)) GB</td>
            <td>$([math]::Round($_.Used/1GB, 1)) GB</td>
            <td>$([math]::Round($_.Free/1GB, 1)) GB</td>
            <td class='$cssClass'>%$pct</td>
        </tr>
"@
}

$htmlBody += @"
    </table>
    <br>
    <p style='color: gray; font-size: 12px;'>Bu rapor otomatik olarak olusturulmustur.</p>
</body>
</html>
"@

Send-MailMessage `
    -To "[email protected]" `
    -From "[email protected]" `
    -Subject "Gunluk Disk Durum Raporu - $($env:COMPUTERNAME)" `
    -Body $htmlBody `
    -BodyAsHtml `
    -SmtpServer "mail.sirketiniz.com" `
    -Encoding UTF8

Ekli Dosya Gönderme

Log dosyaları veya raporlar gibi dosyaları e-postaya eklemek için -Attachments parametresini kullanırsınız:

# Birden fazla log dosyasini topla ve e-postaya ekle
$logPath = "C:Logs"
$yesterday = (Get-Date).AddDays(-1).ToString("yyyy-MM-dd")
$logFiles = Get-ChildItem -Path $logPath -Filter "*$yesterday*" -File

if ($logFiles) {
    $attachmentPaths = $logFiles.FullName
    
    Send-MailMessage `
        -To "[email protected]" `
        -From "[email protected]" `
        -Subject "Gunluk Log Raporu - $yesterday" `
        -Body "Dun olusan log dosyalari ekte gonderilmistir. Toplam $($logFiles.Count) adet dosya bulunmaktadir." `
        -SmtpServer "mail.sirketiniz.com" `
        -Attachments $attachmentPaths `
        -Encoding UTF8
    
    Write-Host "$($logFiles.Count) adet log dosyasi e-posta ile gonderildi."
} else {
    Write-Host "Gonderilecek log dosyasi bulunamadi."
}

Credential Güvenli Saklama

Script’lerinizi otomatize ettiğinizde şifreleri açık metin olarak yazmak son derece tehlikelidir. PowerShell’in güvenli credential saklama yöntemlerini kullanmak gerekir.

Yöntem 1: SecureString ile Şifreli Dosyaya Kaydetme

# Sadece bir kez calistirin - credential dosyasini olusturur
# Bu komutu script'in calisacagi kullanici hesabiyla calistirin!
$credential = Get-Credential
$credential.Password | ConvertFrom-SecureString | Set-Content "C:Scriptssmtp_pass.txt"
$credential.UserName | Set-Content "C:Scriptssmtp_user.txt"

Write-Host "Credential basariyla kaydedildi."

Kaydedilen credential’ı kullanan script:

# Otomatik script icerisinde kullanim
$smtpUser = Get-Content "C:Scriptssmtp_user.txt"
$smtpPassSecure = Get-Content "C:Scriptssmtp_pass.txt" | ConvertTo-SecureString
$credential = New-Object System.Management.Automation.PSCredential($smtpUser, $smtpPassSecure)

Send-MailMessage `
    -To "[email protected]" `
    -From "[email protected]" `
    -Subject "Otomatik Bildirim" `
    -Body "Script basariyla tamamlandi." `
    -SmtpServer "mail.sirketiniz.com" `
    -Port 587 `
    -UseSsl `
    -Credential $credential `
    -Encoding UTF8

Önemli Not: ConvertFrom-SecureString ile oluşturulan şifreli dosya, yalnızca aynı Windows kullanıcı hesabı ve aynı makine üzerinde çözülebilir. Bu nedenle Task Scheduler görevi hangi kullanıcı hesabıyla çalışacaksa, credential dosyasını o hesapla oluşturmanız gerekir.

Gerçek Dünya Senaryosu 2: Windows Servis İzleme

Kritik servisleri izleyip durumu değiştiğinde anında bildirim gönderen bir script:

$smtpServer = "mail.sirketiniz.com"
$from = "[email protected]"
$adminTo = "[email protected]"
$statusFile = "C:Scriptsservice_status.xml"

# Izlenecek servisler
$criticalServices = @("W32Time", "DNS", "Netlogon", "MSSQLSERVER", "LanmanServer")

# Onceki durumu yukle
if (Test-Path $statusFile) {
    $previousStatus = Import-Clixml $statusFile
} else {
    $previousStatus = @{}
}

$currentStatus = @{}
$changedServices = @()

foreach ($serviceName in $criticalServices) {
    $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
    
    if ($service) {
        $currentStatus[$serviceName] = $service.Status
        
        # Durum degistiyse veya servis duruyorsa bildir
        if ($previousStatus.ContainsKey($serviceName)) {
            if ($previousStatus[$serviceName] -ne $service.Status) {
                $changedServices += [PSCustomObject]@{
                    Name = $serviceName
                    DisplayName = $service.DisplayName
                    OldStatus = $previousStatus[$serviceName]
                    NewStatus = $service.Status
                }
            }
        }
    }
}

# Durum degisikligi varsa e-posta gonder
if ($changedServices.Count -gt 0) {
    $bodyLines = @("Asagidaki servislerin durumu degismistir:`n")
    
    foreach ($svc in $changedServices) {
        $bodyLines += "Servis: $($svc.DisplayName) ($($svc.Name))"
        $bodyLines += "Eski Durum: $($svc.OldStatus)"
        $bodyLines += "Yeni Durum: $($svc.NewStatus)"
        $bodyLines += "Zaman: $(Get-Date -Format 'dd.MM.yyyy HH:mm:ss')"
        $bodyLines += "Sunucu: $($env:COMPUTERNAME)"
        $bodyLines += "---"
    }
    
    $priority = if ($changedServices | Where-Object { $_.NewStatus -eq "Stopped" }) { "High" } else { "Normal" }
    
    Send-MailMessage `
        -To $adminTo `
        -From $from `
        -Subject "[SERVIS UYARISI] $($env:COMPUTERNAME) - $($changedServices.Count) servis durum degisikligi" `
        -Body ($bodyLines -join "`n") `
        -SmtpServer $smtpServer `
        -Priority $priority `
        -Encoding UTF8
}

# Mevcut durumu kaydet
$currentStatus | Export-Clixml $statusFile

Birden Fazla Alıcıya E-posta Gönderme

-To, -Cc ve -Bcc parametrelerinin tamamı virgülle ayrılmış string veya string dizisi kabul eder:

$toList = @(
    "[email protected]",
    "[email protected]"
)

$ccList = @(
    "[email protected]"
)

Send-MailMessage `
    -To $toList `
    -Cc $ccList `
    -Bcc "[email protected]" `
    -From "[email protected]" `
    -Subject "Kritik Sistem Uyarisi" `
    -Body "Kritik bir sistem hatasi tespit edildi. Lutfen kontrol ediniz." `
    -SmtpServer "mail.sirketiniz.com" `
    -Priority High `
    -Encoding UTF8

Hata Yönetimi ve Güvenilir E-posta Gönderimi

Üretim ortamlarında e-posta gönderimi başarısız olabilir. Bunu sessizce geçiştirmek yerine düzgün hata yönetimi eklemek gerekir:

function Send-AlertMail {
    param(
        [string]$To,
        [string]$Subject,
        [string]$Body,
        [int]$RetryCount = 3,
        [int]$RetryDelaySeconds = 30,
        [switch]$BodyAsHtml
    )
    
    $smtpServer = "mail.sirketiniz.com"
    $from = "[email protected]"
    $attempt = 0
    $success = $false
    
    while ($attempt -lt $RetryCount -and -not $success) {
        $attempt++
        try {
            $params = @{
                To         = $To
                From       = $from
                Subject    = $Subject
                Body       = $Body
                SmtpServer = $smtpServer
                Encoding   = [System.Text.Encoding]::UTF8
                ErrorAction = "Stop"
            }
            
            if ($BodyAsHtml) {
                $params.BodyAsHtml = $true
            }
            
            Send-MailMessage @params
            $success = $true
            Write-Host "E-posta basariyla gonderildi (Deneme: $attempt)"
            
        } catch {
            Write-Warning "E-posta gonderilemedi (Deneme $attempt/$RetryCount): $($_.Exception.Message)"
            
            if ($attempt -lt $RetryCount) {
                Write-Host "$RetryDelaySeconds saniye bekleniyor..."
                Start-Sleep -Seconds $RetryDelaySeconds
            }
        }
    }
    
    if (-not $success) {
        # E-posta gonderilemezse log dosyasina yaz
        $logEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | HATA | E-posta gonderilemedi | Konu: $Subject | Alici: $To"
        Add-Content -Path "C:Logsmail_errors.log" -Value $logEntry
        Write-Error "E-posta $RetryCount deneme sonrasinda gonderilemedi. Hata log dosyasina yazildi."
    }
    
    return $success
}

# Kullanim ornegi
Send-AlertMail `
    -To "[email protected]" `
    -Subject "Backup Tamamlandi" `
    -Body "Gece backup islemi basariyla tamamlandi. Tarih: $(Get-Date -Format 'dd.MM.yyyy HH:mm')" `
    -RetryCount 3 `
    -RetryDelaySeconds 60

Office 365 ve Modern Kimlik Doğrulama

Kurumunuz Office 365 kullanıyorsa ve temel SMTP kimlik doğrulaması etkinse aşağıdaki ayarlar işe yarar:

$smtpUser = "[email protected]"
$smtpPassSecure = Read-Host "SMTP Sifresi" -AsSecureString
$credential = New-Object System.Management.Automation.PSCredential($smtpUser, $smtpPassSecure)

Send-MailMessage `
    -To "[email protected]" `
    -From $smtpUser `
    -Subject "Office 365 Test" `
    -Body "Office 365 SMTP testi" `
    -SmtpServer "smtp.office365.com" `
    -Port 587 `
    -UseSsl `
    -Credential $credential `
    -Encoding UTF8

Modern kimlik doğrulama (MFA, OAuth2) gereken durumlarda Send-MailMessage yetersiz kalır. Bu durumlar için Microsoft Graph API veya Microsoft.Graph PowerShell modülü kullanılması önerilir, ancak bu konu ayrı bir yazının konusu.

Task Scheduler ile Otomatize Etme

Script’inizi Task Scheduler’a eklerken dikkat etmeniz gerekenler:

  • Görevi çalıştıracak kullanıcı hesabı, credential dosyalarına erişim yetkisine sahip olmalı
  • “Run whether user is logged on or not” seçeneği işaretli olmalı
  • PowerShell execution policy, görevi çalıştıracak hesap için ayarlanmış olmalı
  • Working directory doğru belirtilmeli

Task Scheduler’dan PowerShell script çalıştırma için program alanına şunu girin:

powershell.exe -NonInteractive -ExecutionPolicy Bypass -File "C:Scriptsdisk_monitor.ps1"

Script çıktısını log dosyasına yönlendirmek için:

powershell.exe -NonInteractive -ExecutionPolicy Bypass -File "C:Scriptsdisk_monitor.ps1" >> "C:Logsdisk_monitor.log" 2>&1

Sık Karşılaşılan Sorunlar ve Çözümleri

“The SMTP server requires a secure connection” hatası

Bu hata genellikle SSL/TLS gerekli olduğu halde -UseSsl parametresinin eklenmediğini gösterir. -Port 587 ile birlikte -UseSsl ekleyin.

“5.7.57 SMTP; Client was not authenticated” hatası

Office 365 veya Exchange Online’da Basic Authentication devre dışı bırakılmış olabilir. Exchange admin merkezi üzerinden SMTP AUTH’u ilgili mailbox için etkinleştirmeniz gerekir.

Türkçe karakter bozulması

-Encoding UTF8 parametresini eklemeniz genellikle sorunu çözer. Bazı eski SMTP sunucularında UTF8 yerine unicode deneyebilirsiniz.

“Mailbox unavailable” veya relay hatası

SMTP sunucunuz, From adresindeki domain için relay izni vermeyebilir. Exchange veya SMTP sunucunuzda relay ayarlarını ve gönderen adresini kontrol edin.

Sonuç

Send-MailMessage, Windows ortamlarında e-posta tabanlı bildirimleri uygulamanın en hızlı ve pratik yoludur. Disk uyarılarından servis izlemeye, backup bildirimlerinden günlük raporlara kadar onlarca farklı senaryoda kullanabilirsiniz. Temel kullanımı son derece basit olmasına rağmen, HTML formatlama, ekli dosyalar, retry mekanizması ve güvenli credential yönetimi ile üretim kalitesinde bildirim sistemleri kurabilirsiniz.

Cmdlet’in “obsolete” etiketinden çekinmeyin. Kurumsal Exchange veya iç SMTP relay kullandığınız senaryolarda yıllarca sorunsuz çalışmaya devam edecektir. Office 365 ile modern kimlik doğrulama gerektiren ortamlarda ise Microsoft Graph PowerShell modülüne geçiş yapmanın zamanı gelmiş demektir.

Script’lerinize e-posta bildirimi eklemek, gecenin ortasında sisteminizin durumunu öğrenmek için RDP bağlanma ihtiyacını ortadan kaldırır. Bir kez kurduğunuzda, bu basit otomasyon size sayısız saat kazandıracak.

Yorum yapın