JT.SendMail.psm1
|
# Module-scope flag to avoid loading assemblies more than once $script:MailKitAssembliesLoaded = $false function Import-MailKitAssemblies { [CmdletBinding()] param() if ($script:MailKitAssembliesLoaded) { return } $libPath = Join-Path $PSScriptRoot 'lib' $mimeKitDll = Join-Path $libPath 'MimeKit.dll' $mailKitDll = Join-Path $libPath 'MailKit.dll' if (-not (Test-Path $mimeKitDll)) { throw "MimeKit.dll not found: $mimeKitDll" } if (-not (Test-Path $mailKitDll)) { throw "MailKit.dll not found: $mailKitDll" } # Optional dependency DLLs $optionalDeps = @( 'BouncyCastle.Cryptography.dll', 'BouncyCastle.Crypto.dll', 'System.Buffers.dll', 'System.Memory.dll', 'System.Runtime.CompilerServices.Unsafe.dll', 'System.Numerics.Vectors.dll', 'System.Formats.Asn1.dll' ) foreach ($dep in $optionalDeps) { $depPath = Join-Path $libPath $dep if (Test-Path $depPath) { [void][System.Reflection.Assembly]::LoadFrom($depPath) } } # Load MimeKit first, then MailKit [void][System.Reflection.Assembly]::LoadFrom($mimeKitDll) [void][System.Reflection.Assembly]::LoadFrom($mailKitDll) $script:MailKitAssembliesLoaded = $true } function Convert-HtmlToPlainText { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Html ) $text = $Html -replace '<style[\s\S]*?</style>', ' ' $text = $text -replace '<script[\s\S]*?</script>', ' ' $text = $text -replace '<[^>]+>', ' ' $text = $text -replace ' ', ' ' $text = $text -replace '&', '&' $text = $text -replace '<', '<' $text = $text -replace '>', '>' $text = $text -replace '\s+', ' ' return $text.Trim() } function Add-AddressesToCollection { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [object]$Collection, [Parameter(Mandatory = $true)] [string[]]$Addresses ) foreach ($addr in $Addresses) { if ([string]::IsNullOrWhiteSpace($addr)) { continue } $Collection.Add([MimeKit.MailboxAddress]::Parse($addr)) } } function Set-MimeMessagePriority { param ( [Parameter(Mandatory = $true)] [MimeKit.MimeMessage]$Message, [Parameter(Mandatory = $true)] [ValidateSet('High', 'Normal', 'Low')] [string]$PriorityLevel ) switch ($PriorityLevel) { 'High' { $Message.Importance = [MimeKit.MessageImportance]::High $Message.Priority = [MimeKit.MessagePriority]::Urgent $Message.XPriority = [MimeKit.XMessagePriority]::High } 'Low' { $Message.Importance = [MimeKit.MessageImportance]::Low $Message.Priority = [MimeKit.MessagePriority]::NonUrgent $Message.XPriority = [MimeKit.XMessagePriority]::Low } 'Normal' { $Message.Importance = [MimeKit.MessageImportance]::Normal $Message.Priority = [MimeKit.MessagePriority]::Normal $Message.XPriority = [MimeKit.XMessagePriority]::Normal } } } function Send-MailKitMessage { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$From, [Parameter(Mandatory = $true)] [string[]]$To, [Parameter(Mandatory = $false)] [string[]]$Cc, [Parameter(Mandatory = $false)] [string[]]$Bcc, [Parameter(Mandatory = $false)] [string[]]$ReplyTo, [Parameter(Mandatory = $true)] [string]$Subject, [Parameter(Mandatory = $false)] [string]$Body, [Parameter(Mandatory = $false)] [switch]$BodyAsHtml, [Parameter(Mandatory = $false)] [string]$TextBody, [Parameter(Mandatory = $false)] [string]$HtmlBody, [Parameter(Mandatory = $false)] [string[]]$Attachments, [Parameter(Mandatory = $true)] [string]$SmtpServer, [Parameter(Mandatory = $false)] [int]$Port = 25, [Parameter(Mandatory = $false)] [ValidateSet('None','Auto','SslOnConnect','StartTls','StartTlsWhenAvailable')] [string]$SecureSocketOption = 'Auto', [Parameter(Mandatory = $false)] [pscredential]$Credential, [Parameter(Mandatory = $false)] [switch]$SkipCertificateValidation, [Parameter(Mandatory = $false)] [ValidateSet('High', 'Normal', 'Low')] [string]$PriorityLevel = 'Normal' ) Import-MailKitAssemblies $message = [MimeKit.MimeMessage]::new() # From $message.From.Add([MimeKit.MailboxAddress]::Parse($From)) # Recipients Add-AddressesToCollection -Collection $message.To -Addresses $To if ($Cc) { Add-AddressesToCollection -Collection $message.Cc -Addresses $Cc } if ($Bcc) { Add-AddressesToCollection -Collection $message.Bcc -Addresses $Bcc } if ($ReplyTo) { Add-AddressesToCollection -Collection $message.ReplyTo -Addresses $ReplyTo } Set-MimeMessagePriority -Message $message -PriorityLevel $PriorityLevel $message.Subject = $Subject $builder = [MimeKit.BodyBuilder]::new() # Body handling priority: # 1. Explicit TextBody / HtmlBody # 2. Fallback to Body + BodyAsHtml if ($PSBoundParameters.ContainsKey('TextBody')) { $builder.TextBody = $TextBody } if ($PSBoundParameters.ContainsKey('HtmlBody')) { $builder.HtmlBody = $HtmlBody if (-not $PSBoundParameters.ContainsKey('TextBody') -and -not [string]::IsNullOrWhiteSpace($HtmlBody)) { $builder.TextBody = Convert-HtmlToPlainText -Html $HtmlBody } } if (-not $PSBoundParameters.ContainsKey('TextBody') -and -not $PSBoundParameters.ContainsKey('HtmlBody')) { if ($BodyAsHtml) { $builder.HtmlBody = $Body $builder.TextBody = Convert-HtmlToPlainText -Html $Body } else { $builder.TextBody = $Body } } # Attachments if ($Attachments) { foreach ($file in $Attachments) { if (-not (Test-Path -LiteralPath $file)) { throw "Attachment not found: $file" } $null = $builder.Attachments.Add($file) } } $message.Body = $builder.ToMessageBody() $socketOption = [MailKit.Security.SecureSocketOptions]::$SecureSocketOption $client = [MailKit.Net.Smtp.SmtpClient]::new() try { if ($SkipCertificateValidation) { $client.ServerCertificateValidationCallback = { $true } } $client.Connect($SmtpServer, $Port, $socketOption) # Authenticate only if credential is provided if ($Credential) { $plainPassword = [System.Net.NetworkCredential]::new('', $Credential.Password).Password $client.Authenticate($Credential.UserName, $plainPassword) } $serverResponse = $client.Send($message) [pscustomobject]@{ Success = $true SmtpServer = $SmtpServer Port = $Port From = $From To = ($To -join '; ') Subject = $Subject MessageId = $message.MessageId ServerResponse = $serverResponse } } catch { throw "Failed to send email via '$($SmtpServer):$($Port)'. $($_.Exception.Message)" } finally { if ($client) { if ($client.IsConnected) { $client.Disconnect($true) } $client.Dispose() } } } Export-ModuleMember -Function Send-MailKitMessage |