PowerShellで Gmail/Yahoo!JAPAN SMTPを利用したメール送信

# はじめに

PowerShellからメール送信ができるのか気になりました。
まあ、できるんでしょうけど、実際に手を動かして体験してみたかったので、調べてみました。
GmailのSMTPサーバを利用したメール送信は、簡単に実現できましたが、Yahoo!JAPANのSMTPサーバを利用したメール送信に苦労したので、メモを残しておきます。

PowerShell でメール送信を実現するための、主要なコマンドレットやクラスは、次を利用しました。
Microsoftのサイトを見ると、どれも非推奨になっていたけど、他の選択肢が限られるので、使ってみました。

- Send-MailMessageコマンドレット
  - [Send-MailMessage (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Docs](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/send-mailmessage)
  - 当コマンドレットの-Portオプションを使うには、PowerShell 3.0 以降が必要。
  - GmailのSMTPサーバを利用可能。Yahoo!JAPANのSMTPサーバを使うことはできなかった。
- SmtpClientクラス
  - [SmtpClient クラス (System.Net.Mail) | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.mail.smtpclient)
  - .NET Framework 2.0 以降（なので PowerShell 2.0 以降）
  - データ転送方式は、STARTTLSのみで、SMTP over SSLには対応していないらしい。
  - GmailのSMTPサーバを利用可能。Yahoo!JAPANのSMTPサーバを使うことはできなかった。
- SmtpMailクラス
  - [SmtpMail クラス (System.Web.Mail) | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/api/system.web.mail.smtpmail)
  - .NET Framework 1.1 以降（なので PowerShell 1.0 以降）で使用可能だが、使用を推奨されていない。
  - データ転送方式は、STARTTLSではなく、SMTP over SSLとなるらしい。
  - GmailとYahoo!JAPANのSMTPサーバを利用可能。

# メール送信の実装

## Send-MailMessageコマンドレット

下記のように記述して、GmailのSMTPサーバを利用したメール送信ができました。

SMTPサーバへの接続ポートは、TLS/STARTTLSのポート「587」を使用しました。
SSLのポート「465」だと、エラーになってしまいました。
私の書き方が悪いのか、Send-MailMessageコマンドレットが対応していないのか、原因は分かりません。

SMTPサーバ接続の認証情報として利用するGmailのアカウントですが、次の2パターンで利用可能でした。

- 2段階認証を設定されていないアカウントを使用する。
- 2段階認証を設定しているアカウントで、アプリパスワードを払い出し、下記コードの認証情報のパスワードに設定する。

認証情報として設定する下記「Gmailのアカウント名」は、ネットでいくつか実装例をみると、「@gmail.com」を付けないのが多い気がしました。
でも、「@gmail.com」を付けなくても問題なく動作しました。

Yahoo!JAPANのSMTPサーバを使うとエラーになってしまいました。

```powershell
$password = ConvertTo-SecureString "Gmailのパスワード" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential(
  "Gmailのアカウント名", $password)
$from = "送信元のGmailメールアドレス"
$to = "送信先メールアドレス"
Send-MailMessage `
  -From $from `
  -To $to `
  -Subject "テストメール" `
  -Body "テストメールです。" `
  -Attachments "D:\tmp\dummy.txt" `
  -Encoding UTF8 `
  -SmtpServer "smtp.gmail.com" `
  -Port 587 `
  -UseSsl `
  -Credential $credential
```

以下は、失敗例です。

```powershell
# SMTPサーバへの接続ポートに、SSLポート「465」を使用するとエラーになってしまいました。
Send-MailMessage `
  -From $from `
  -To $to `
  -Subject "テストメール" `
  -Body "テストメールです。" `
  -Encoding UTF8 `
  -SmtpServer "smtp.gmail.com" `
  -Port 465 `
  -UseSsl `
  -Credential $credential
#=> 転送接続からデータを読み取れません: net_io_connectionclosed
```

```powershell
# 2段階認証を設定しアプリパスワードを払い出していないアカウントを使用した。
Send-MailMessage `
  -From $from `
  -To $to `
  -Subject "テストメール" `
  -Body "テストメールです。" `
  -Encoding UTF8 `
  -SmtpServer "smtp.gmail.com" `
  -Port 587 `
  -UseSsl `
  -Credential $credential
#=> SMTP サーバーにセキュリティで保護された接続が必要であるか、またはクライアントが認証されていません。 サーバーの応答:5.7.0 Authentication Required.
```

```powershell
# Yahoo!JAPANのSMTPサーバを使用
$password = ConvertTo-SecureString "Yahoo!JAPANのパスワード" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential(
  "Yahoo!JAPANのアカウント名", $password)
Send-MailMessage `
  -From $from `
  -To $to `
  -Subject "テストメール" `
  -Body "テストメールです。" `
  -Encoding UTF8 `
  -SmtpServer "smtp.mail.yahoo.co.jp" `
  -Port 465 `
  -UseSsl `
  -Credential $credential
#=> 転送接続からデータを読み取れません: net_io_connectionclosed
```

## SmtpClientクラス

以下のようにして、GmailのSMTPサーバでTLS/STARTTLSのポート「587」を使用したメール送信ができました。
ですが、GmailのSMTPサーバでSSLのポート「465」を使用した場合や、Yahoo!JAPANのSMTPサーバを使用した場合は、エラーとなってしまいました。

Send-MailMessageコマンドレットとの違いは、Send-MailMessageコマンドレットよりも古い環境でも動作すること。

```powershell
$user = "Gmailのアカウント名"
$password = "Gmailのパスワード"
$from = "送信元のGmailメールアドレス"
$to = "送信先メールアドレス"
$client = New-Object Net.Mail.SmtpClient("smtp.gmail.com", 587)
$client.EnableSsl = $true
$client.Credentials = New-Object System.Net.NetworkCredential($user, $password)
$client.Send($from, $to, "テストメール", "テストメールです。")
$client.Dispose()
```

次は、失敗例です。

```powershell
# GmailのSMTPサーバでSSLポート「465」
$client = New-Object Net.Mail.SmtpClient("smtp.gmail.com", 465)
$client.EnableSsl = $true
$client.Credentials = New-Object System.Net.NetworkCredential($user, $password)
$client.Send($from, $to, "テストメール", "テストメールです。")
#=> "4" 個の引数を指定して "Send" を呼び出し中に例外が発生しました: "メールを送信できませんでした。"
```

```powershell
# Yahoo!JAPANのSMTPサーバ
$user = "Yahoo!JAPANのアカウント名"
$password = "Yahoo!JAPANのパスワード"
$from = "送信元のYahoo!JAPANメールアドレス"
$client = New-Object Net.Mail.SmtpClient("smtp.mail.yahoo.co.jp", 465)
$client.EnableSsl = $true
$client.Credentials = New-Object System.Net.NetworkCredential($user, $password)
$client.Send($from, $to, "テストメール", "テストメールです。")
#=> "4" 個の引数を指定して "Send" を呼び出し中に例外が発生しました: "メールを送信できませんでした。"
```

## SmtpMailクラス

この方法で、Yahoo!JAPANのSMTPサーバを使用することができました。
GmailのSMTPサーバについては、SSLのポート「465」で使用できましたが、TLS/STARTTLSのポート「587」では使用できませんでした。

SmtpMailクラスでは、データ転送方式がSTARTTLSではなく、SMTP over SSLとなるらしいので、SSLポート「465」しか使用できないのかもしれません。

```powershell
Add-Type -AssemblyName System.Web
$to = "送信先メールアドレス"

# GmailのSMTPサーバをSSLポート「465」で使用
$user = "Gmailのアカウント名"
$password = "Gmailのパスワード"
$from = "送信元のGmailメールアドレス"
$mail = New-Object System.Web.Mail.MailMessage
$mail.From = $from
$mail.To = $to
$mail.Subject = "テストメール"
$mail.Body = "テストメールです。"
$mail.Attachments.Add(
  (New-Object System.Web.Mail.MailAttachment("D:\tmp\dummy.txt", [System.Web.Mail.MailEncoding]::Base64))
  ) | Out-Null
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendusing"] = 2 # 外部メールサーバへの接続
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserver"] = "smtp.gmail.com"
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserverport"] = 465
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpauthenticate"] = 1 # 1：Basic認証、2：NTLM認証
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendusername"] = $user
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendpassword"] = $password
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpusessl"] = $true # SMTP over SSL
[System.Web.Mail.SmtpMail]::SmtpServer = "smtp.gmail.com"
[System.Web.Mail.SmtpMail]::Send($mail)
```

```powershell
# Yahoo!JAPANのSMTPサーバを使用
$user = "Yahoo!JAPANのアカウント名"
$password = "Yahoo!JAPANのパスワード"
$from = "送信元のYahoo!JAPANメールアドレス"
$mail = New-Object System.Web.Mail.MailMessage
$mail.From = $from
$mail.To = $to
$mail.Subject = "テストメール"
$mail.Body = "テストメールです。"
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendusing"] = 2
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserver"] = "smtp.mail.yahoo.co.jp"
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserverport"] = 465
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpauthenticate"] = 1
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendusername"] = $user
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendpassword"] = $password
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpusessl"] = $true
[System.Web.Mail.SmtpMail]::SmtpServer = "smtp.mail.yahoo.co.jp"
[System.Web.Mail.SmtpMail]::Send($mail)
```

次は、失敗例です。

```powershell
# GmailのSMTPサーバをTLS/STARTTLSポート「587」で使用
$mail = New-Object System.Web.Mail.MailMessage
$mail.From = $from
$mail.To = $to
$mail.Subject = "テストメール"
$mail.Body = "テストメールです。"
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendusing"] = 2
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserver"] = "smtp.gmail.com"
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserverport"] = 587
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpauthenticate"] = 1
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendusername"] = $user
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendpassword"] = $password
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpusessl"] = $true
[System.Web.Mail.SmtpMail]::SmtpServer = "smtp.gmail.com"
[System.Web.Mail.SmtpMail]::Send($mail)
#=> "1" 個の引数を指定して "Send" を呼び出し中に例外が発生しました: "転送においてサーバーに接続できませんでした。
```

```powershell
# GmailのSMTPサーバをTLS/STARTTLSのポート「587」で使用。smtpusessl=false
$mail = New-Object System.Web.Mail.MailMessage
$mail.From = $from
$mail.To = $to
$mail.Subject = "テストメール"
$mail.Body = "テストメールです。"
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendusing"] = 2
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserver"] = "smtp.gmail.com"
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserverport"] = 587
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpauthenticate"] = 1
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendusername"] = $user
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/sendpassword"] = $password
$mail.Fields["http://schemas.microsoft.com/cdo/configuration/smtpusessl"] = $false
[System.Web.Mail.SmtpMail]::SmtpServer = "smtp.gmail.com"
[System.Web.Mail.SmtpMail]::Send($mail)
#=> "1" 個の引数を指定して "Send" を呼び出し中に例外が発生しました: "サーバーによって送信者アドレスが拒否されました。サーバーからの応答は次のとおりです。530 5.7.0 Must issue a STARTTLS command first. w1sm5388841pfu.153 - gsmtp"
```

# メール送信に関連する処理

## SMTPサーバ認証情報を暗号化してファイル管理

コード内に認証情報を含めてしまうと、セキュリティ的に問題があるので、暗号化してファイル保管する方法をメモしておきます。
Get-Credential、ConvertFrom-SecureString、ConvertTo-SecureStringコマンドレットを使用しました。

```powershell
# ファイル保存
$path = "D:\tmp\password.json"
$credential = Get-Credential
ConvertTo-Json @{
  userId = $credential.UserName;
  password = $credential.Password | ConvertFrom-SecureString;
  } | Set-Content $path

# ファイル読み込み
$jsonObj = Get-Content $path | ConvertFrom-Json
$password = $jsonObj.password | ConvertTo-SecureString
$credential = New-Object System.management.Automation.PsCredential($jsonObj.userId, $password)

# メール送信
$from = "送信元のGmailメールアドレス"
$to = "送信先メールアドレス"
Send-MailMessage `
  -From $from `
  -To $to `
  -Subject "テストメール" `
  -Body "テストメールです。" `
  -Encoding UTF8 `
  -SmtpServer "smtp.gmail.com" `
  -Port 587 `
  -UseSsl `
  -Credential $credential
```

## SMTPサーバのポート確認

メール送信処理でエラーが出たとき、ポートが開いていることを確認するためには、次のコマンドレットが使えました。

```powershell
Test-NetConnection "smtp.gmail.com" -port 465         # OK
Test-NetConnection "smtp.gmail.com" -port 587         # OK
Test-NetConnection "smtp.mail.yahoo.co.jp" -port 465  # OK
Test-NetConnection "smtp.mail.yahoo.co.jp" -port 587  # NG
Test-NetConnection "smtp.mail.yahoo.co.jp" -port 25   # NG
```

# 補足

## 動作確認環境

- Windows 10
- PowerShell 5.1

## 関連する用語・情報

- SMTP
  - ポート 25
- SMTPS (SMTP over SSL)
  - ポート 465
  - TLS (Transport Layer Security)を用いてSMTPをセキュアにする手法。
- Transport Layer Security (TLS)
  - [Transport Layer Security - Wikipedia](https://ja.wikipedia.org/wiki/Transport_Layer_Security)
  - 1999年に TLS 1.0 が公開され、2020年現在の最新は TLS 1.3。
- Secure Sockets Layer (SSL)
- STARTTLS
  - [STARTTLS - Wikipedia](https://ja.wikipedia.org/wiki/STARTTLS)
  - 平文の通信プロトコルを暗号化通信に拡張する方法のひとつ。
  - 2002年頃から規定されている模様。
- GmailのSMTPサーバ
  - [他のメール プラットフォームで Gmail のメールをチェックする - Gmail ヘルプ](https://support.google.com/mail/answer/7126229)
  - SMTPサーバ：smtp.gmail.com、SSLのポート：465、TLS/STARTTLSのポート：587
  - 認証で使用するアカウントでは、2段階認証を設定していないこと。または、2段階認証を設定しているアカウントで、アプリパスワードを払い出すこと。
- Yahoo!JAPANのSMTPサーバ
  - [メールソフトで送受信するには（Yahoo!メールアドレスの場合）](https://support.yahoo-net.jp/PccMail/s/article/H000007321)
  - [新着情報ページ - Yahoo!メール](https://whatsnewmail.yahoo.co.jp/yahoo/20210119a.html)
  - SMTPサーバ：smtp.mail.yahoo.co.jp、SSLのポート：465
  - 2021/1/19で、SMTPサーバ接続時の非暗号化ポート（ポート番号 25, 587）の提供が終了した。
  - 認証で使用するアカウントのメール設定で、「IMAP/POP/SMTPアクセスとメール転送」のSMTP設定を有効にする。

## 参考にしたサイト

- [Send-MailMessage (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Docs](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/send-mailmessage)
- [SmtpClient クラス (System.Net.Mail) | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.mail.smtpclient)
- [SmtpMail クラス (System.Web.Mail) | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/api/system.web.mail.smtpmail)
- [【PowerShell】Windows PowerShell を使用して GMail や Office 365 からメールを送信する | Microsoft Docs](https://docs.microsoft.com/ja-jp/archive/blogs/junichia/powershellwindows-powershell-gmail-office-365)
- [SMTPでメールを送信する - .NET Tips (VB.NET,C#...)](https://dobon.net/vb/dotnet/internet/smtpmail.html#section3)
- [SSL/TLSを使用してSMTPでメールを送信する - .NET Tips (VB.NET,C#...)](https://dobon.net/vb/dotnet/internet/smtpssltls.html)
- [【Windows】メーラーやWebメールを使わずにメールを送信する ? スイーツ好きエンジニアの備忘録](https://it-memorandum.net/archives/1024)