開発用SMTPサーバーsmtp4devでTLS通信を行う 


# smtp4dev とは
smtp4devは、開発用にSMTPサーバーをエミュレートしてくれるダミーサーバーです。

https://github.com/rnwood/smtp4dev

## 参考記事

https://qiita.com/yaginagi/items/92c89bac083f050363b6

https://pleasanter.hatenablog.jp/entry/2019/03/19/153834

# TLS通信でメールを送信する
smtp4devをインストール後の初期設定ではTLS通信が無効となっていて、TLS通信で動作確認を行う場合は、いくつか手順を踏む必要がありました。
今回は、その手順について共有します。

## 動作環境
- smtp4dev version 3.1.3-ci20211206101
- Windows11
- メール送信のクライアントには [MailKit](https://github.com/jstedfast/MailKit) を利用した .NET6.0 コンソールアプリケーションを利用しました。（下記サンプルコードを参照）

### サンプルコード

参考までに、サンプルコードの作成手順を載せておきます。
※ 手順は、Visual Studio Code と、.NET6.0のSDKがインストールされているこを前提としています。
※ "C:¥myproject"配下にプロジェクトフォルダ"SmtpTest"を作成するのもとします。


1. SmtpTestフォルダを作成して移動
```powershell
PS C:\myproject> mkdir SmtpTest

    Directory: C:\myproject

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          2022/02/15    19:43                SmtpTest

PS C:\myproject> cd .\SmtpTest\
```
2. dotnetコマンドでコンソールアプリを作成

```powershell
PS C:\myproject\SmtpTest> dotnet new console
テンプレート "コンソール アプリ" が正常に作成されました。

作成後の操作を処理しています...
C:\myproject\SmtpTest\SmtpTest.csproj で ' dotnet restore ' を実行しています...
  復元対象のプロジェクトを決定しています...
  C:\myproject\SmtpTest\SmtpTest.csproj を復元しました (57 ms)。
正常に復元されました。
```

3. NuGetパッケージMailKitをインストール

```powershell
PS C:\myproject\SmtpTest> dotnet add package MailKit
  復元対象のプロジェクトを決定しています...
  Writing C:\Users\hogehoge\AppData\Local\Temp\tmp8588.tmp
info : パッケージ 'MailKit' の PackageReference をプロジェクト 'C:\myproject\SmtpTest\SmtpTest.csproj' に追加しています。
info :   GET https://api.nuget.org/v3/registration5-gz-semver2/mailkit/index.json
info :   OK https://api.nuget.org/v3/registration5-gz-semver2/mailkit/index.json 671 ミリ秒
info :   GET https://api.nuget.org/v3/registration5-gz-semver2/mailkit/page/0.1.0/1.2.8.json
info :   OK https://api.nuget.org/v3/registration5-gz-semver2/mailkit/page/0.1.0/1.2.8.json 975 ミリ秒
info :   GET https://api.nuget.org/v3/registration5-gz-semver2/mailkit/page/1.2.9/2.1.2.json
info :   OK https://api.nuget.org/v3/registration5-gz-semver2/mailkit/page/1.2.9/2.1.2.json 628 ミリ秒
info :   GET https://api.nuget.org/v3/registration5-gz-semver2/mailkit/page/2.1.3/3.1.1.json
info :   OK https://api.nuget.org/v3/registration5-gz-semver2/mailkit/page/2.1.3/3.1.1.json 645 ミリ秒
info : C:\myproject\SmtpTest\SmtpTest.csproj のパッケージを復元しています...
info : パッケージ 'MailKit' は、プロジェクト 'C:\myproject\SmtpTest\SmtpTest.csproj' のすべての指定されたフレームワークとの互換性があります。
info : ファイル 'C:\myproject\SmtpTest\SmtpTest.csproj' に追加されたパッケージ 'MailKit' バージョン '3.1.1' の PackageReference。
info : 復元をコミットしています...
info : アセット ファイルをディスクに書き込んでいます。パス: C:\myproject\SmtpTest\obj\project.assets.json
log  : C:\myproject\SmtpTest\SmtpTest.csproj を復元しました (67 ms)。
```
4. Visual Studio Code でフォルダを開く

```powershell
PS C:\myproject\SmtpTest> code .
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/5ac36e67-1c35-cb34-5d86-85f9bf08bdcf.png)

5. Program.cs を下記のコードに書き換える

```csharp
using MimeKit;
using MailKit.Net.Smtp;

var message = new MimeMessage()
{
    Subject = "テストメールのタイトル",
    Body = new TextPart(MimeKit.Text.TextFormat.Plain)
    {
        Text = "テストメールの本文"
    }
};
message.From.Add(new MailboxAddress("送信太郎","taro@example.com"));
message.To.Add(new MailboxAddress("受信次郎","jiro@example.com"));

using var smtpClient = new SmtpClient();
await smtpClient.ConnectAsync("localhost", 25);
await smtpClient.AuthenticateAsync("testUser","dummy-pass");
await smtpClient.SendAsync(message);
await smtpClient.DisconnectAsync(true);
```

6. dotnet run で実行

```powershell
PS C:\myproject\SmtpTest> dotnet run
```

## smtp4devの設定

smtp4devを起動すると、「%AppData%\smtp4dev」フォルダにユーザー毎の設定ファイルapplication.jsonが作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/f792fb70-d1a0-0153-720d-e0ffc17578da.png)


一旦smtp4devを終了して、このファイルを開きます。

以下の２項目を下記の様に設定します。
- HostName: "localhost"
- TlsMode: "StartTls"

```json
{
  "ServerOptions": {
    "Port": 25,
    "AllowRemoteConnections": true,
    "Database": "database.db",
    "NumberOfMessagesToKeep": 100,
    "NumberOfSessionsToKeep": 100,
    "BasePath": "/",
    "TlsMode": "StartTls",
    "TlsCertificate": "",
    "TlsCertificatePrivateKey": "",
    "HostName": "localhost",
    "ImapPort": 143,
    "RecreateDb": false
  },
  "RelayOptions": {
    "IsEnabled": false,
    "SmtpServer": "",
    "SmtpPort": 25,
    "TlsMode": 1,
    "AutomaticEmails": [
      ""
    ],
    "SenderAddress": "",
    "Login": "",
    "Password": ""
  }
}
```

ファイルを保存後、smtp4devを再び起動します。

この状態でメールの送信を実行してみると、下記のようなエラーが表示され送信に失敗します。
```cmd
Unhandled exception. MailKit.Security.SslHandshakeException: An error occurred while attempting to establish an SSL or TLS connection.

The server's SSL certificate could not be validated for the following reasons:
? The server certificate has the following errors:
  ? 証明書チェーンは処理されましたが、信頼プロバイダーが信頼していないルート証明書で強制終了しました。

 ---> System.Security.Authentication.AuthenticationException: The remote certificate was rejected by the provided RemoteCertificateValidationCallback.
   at System.Net.Security.SslStream.SendAuthResetSignal(ProtocolToken message, ExceptionDispatchInfo exception)
   at System.Net.Security.SslStream.CompleteHandshake(SslAuthenticationOptions sslAuthenticationOptions)
   at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
   at MailKit.Net.Smtp.SmtpClient.ConnectAsync(String host, Int32 port, SecureSocketOptions options, Boolean doAsync, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at MailKit.Net.Smtp.SmtpClient.ConnectAsync(String host, Int32 port, SecureSocketOptions options, Boolean doAsync, CancellationToken cancellationToken)
   at Program.<Main>$(String[] args) in C:\myproject\MailTest\Program.cs:line 17
   at Program.<Main>(String[] args)
PS C:\myproject\MailTest>
```

smtp4devは、TLSで通信を行う際に自己証明書を自動生成し、それを使用しているのですが、クライアント側（ここではMailKitのSmtpClient）が自己証明書を受け付けないために、このエラーが発生しているものと考えれます。

この問題は、開発用PCに自己証明書をインストールすることで解決します。

## 自己証明書のインストール

先ほどのappsettings.jsonが作成されたフォルダ「%AppData%\smtp4dev」をもう一度見てみると、公開証明書のファイル「selfsigned-certificate.cer」が追加されているのが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/d4941a55-fac2-3a09-d7b3-b95c1be647bf.png)

このファイルをダブルクリックしてインストールを行います。

1. 「証明書のインストール...」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/f2f58234-3dfa-3a81-5d72-1bcc28b9a328.png)

2. 「現在のユーザー」を選択して「次へ」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/62594b94-91ab-a7eb-c625-4286e641171b.png)

3. 「証明書をすべて次のストアに配置する」を選択し、「参照...」から「信頼されたルート証明機関」を選択。「次へ」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/c7ae6097-a92a-b192-c53e-eb9f19fe5bc2.png)

4. 「完了」をクリック

## 動作確認

これで、もう一度送信してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/9bde9da3-728c-9a0e-37fd-3dbb625fb61f.png)


「https:localhost:5001」にアクセスし、メールが届いていればOKです。