.NET 7 使用 MailKit 寄送 email 教學

以前都使用 System.Net.Mail.SmtpClient 來發送 email,但他被標記為已過時,並且在 .NET Core 中不推薦使用。

本篇就使用在官方說明文件中推薦使用的 jstedfast/MailKit 套件來示範傳送 email

安裝套件

先使用 NuGet 安裝 MailKit 套件,或是使用 .NET CLI 執行以下指令安裝
	
dotnet add package MailKit
    

程式碼:

string host = "smtp.gmail.com"; // 送信郵件主機
int port = 587; // 送信郵件主機連接埠
string account = "ruyut_mail_server@gmail.com"; // 帳號
string password = "ruyut_mail_server123"; // 密碼

string mailServerName = "Server"; // 寄信者名稱
string mailServerAddress = "ruyut_mail_server@gmail.com"; // 寄送者信箱
string targetAddress = "a@ruyut.com"; // 目標信箱

string subject = "測試信件"; // 信件主旨
string body = "這是一封測試信件"; // 信件內容


MimeMessage message = new();
message.From.Add(new MailboxAddress(mailServerName, mailServerAddress));
message.To.Add(MailboxAddress.Parse(targetAddress));
message.Subject = subject;
message.Body = new TextPart("html")
{
    Text = body
};

using SmtpClient client = new();
client.Connect(host, port, false);
client.Authenticate(account, password);
client.Send(message);
client.Disconnect(true);
    

註:寄送者信箱和寄信者名稱不一定會被送信伺服器所接受,部分送信伺服器會以登入的使用者帳號傳送。

ㄜ... Email 並沒有被寄出去,出現下面的錯誤
    
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      MailKit.Security.AuthenticationException: 535: 5.7.8 Username and Password not accepted. Learn more at
5.7.8  https://support.google.com/mail/?p=BadCredentials z22-20020aa79596000000b00000000000000000000000000.25 - gsmtp
       ---> MailKit.Net.Smtp.SmtpCommandException: 5.7.8 Username and Password not accepted. Learn more at
5.7.8  https://support.google.com/mail/?p=BadCredentials z22-20020aa79596000000b00000000000000000000000000.25 - gsmtp
--- End of inner exception stack trace --- at MailKit.Net.Smtp.SmtpClient.Authenticate(Encoding encoding, ICredentials credentials, CancellationToken cancellationToken) at MailKit.MailService.Authenticate(Encoding encoding, String userName, String password, CancellationToken cancellationToken) at MailKit.MailService.Authenticate(String userName, String password, CancellationToken cancellationToken) at WebApplicationSentEmailTest.Service.MailService.Sent() in C:\Users\ruyut\Documents\RiderProjects\test\2022\WebApplicationSentEmailTest\WebApplicationSentEmailTest\Service\MailService.cs:line 46 at WebApplicationSentEmailTest.Controllers.RuyutController.Sent() in C:\Users\ruyut\Documents\RiderProjects\test\2022\WebApplicationSentEmailTest\WebApplicationSentEmailTest\Controllers\RuyutController.cs:line 20 at Microsoft.Extensions.Internal.ObjectMethodExecutor.<>c__DisplayClass33_0.<WrapVoidMethod>b__0(Object target, Object[] parameters) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.VoidResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync() at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

原因是因為 2022 年 5 月 30 日起不再支援第三方應用程式只使用帳號密碼登入,解決方式是使用應用程式密碼登入。

取得 Google 的應用程式密碼

要使用應用程式密碼一定要開啟兩步驟驗證,可以進入 Google 帳戶 點選安全性 > 兩步驟驗證

依照步驟完成兩步驟驗證

回到剛剛的頁面會發現「兩步驟驗證」下面多了一個「應用程式密碼」

點選後在「選擇應用程式」選擇「其他」

輸入完後點選「產生」

將上面的 16 個英文字密碼記錄下來

然後將上面程式碼中原本的密碼替換為這 16 個英文即可。

增加附件


string host = "smtp.gmail.com"; // 送信郵件主機
int port = 587; // 送信郵件主機連接埠
string account = "ruyut_mail_server@gmail.com"; // 帳號
string password = "ruyut_mail_server123"; // 密碼

string mailServerName = "Server"; // 寄信者名稱
string mailServerAddress = "ruyut_mail_server@gmail.com"; // 寄送者信箱
string targetAddress = "a@ruyut.com"; // 目標信箱

string subject = "測試信件"; // 信件主旨
string body = "這是一封測試信件"; // 信件內容


MimeMessage message = new();
message.From.Add(new MailboxAddress(mailServerName, mailServerAddress));
message.To.Add(MailboxAddress.Parse(targetAddress));
message.Subject = subject;

BodyBuilder bodyBuilder = new BodyBuilder
{
    HtmlBody = body
};
bodyBuilder.Attachments.Add(@"C:\Users\ruyut\Desktop\test.txt");
bodyBuilder.Attachments.Last().ContentDisposition.FileName = "test.txt";

message.Body = bodyBuilder.ToMessageBody();

using SmtpClient client = new();
client.Connect(host, port, false);
client.Authenticate(account, password);
client.Send(message);
client.Disconnect(true);
    


參考資料:
mimekit.Doc

留言