.Net使用MimeKit发送邮件
13
2022-03-28
网站需要用到邮件功能给用户发送验证码,查了一些资料,测试了各种发送邮件的第三方依赖,发现MimeKit是最好用的,于是封装了一个依赖注入的邮件服务。测试的过程中,发现了MimeKit在添加中文名称的邮箱附件时,有乱码问题,经过各种咨询和测试,发现属于编码问题,mark。
Nuget下载此依赖。
Install-Package MailKit
1.接口
/// <summary>
/// 邮箱服务
/// </summary>
public interface IEMailService
{
/// <summary>
/// 发送邮件
/// </summary>
/// <param name="title">标题</param>
/// <param name="body">内容</param>
/// <param name="name">目标用户名</param>
/// <param name="address">目标邮箱地址</param>
/// <returns></returns>
bool Send(string title, string body, string name, string address);
/// <summary>
/// 发送邮件
/// </summary>
/// <param name="title">标题</param>
/// <param name="body">内容</param>
/// <param name="name">目标用户名</param>
/// <param name="address">目标邮箱地址</param>
/// <returns></returns>
Task<bool> SendAsync(string title, string body, string name, string address);
/// <summary>
/// 发送邮件
/// </summary>
/// <param name="title">标题</param>
/// <param name="body">内容</param>
/// <param name="receives">接收目标</param>
/// <returns></returns>
bool Send(string title, string body, params (string name, string address)[] receives);
/// <summary>
/// 发送邮件
/// </summary>
/// <param name="title">标题</param>
/// <param name="body">内容</param>
/// <param name="receives">接收目标</param>
/// <returns></returns>
Task<bool> SendAsync(string title, string body, params (string name, string address)[] receives);
/// <summary>
/// 发送携带附件的邮件
/// </summary>
/// <param name="title">标题</param>
/// <param name="body">内容</param>
/// <param name="name">目标用户名</param>
/// <param name="address">目标邮箱地址</param>
/// <param name="attachments">附件</param>
/// <returns></returns>
bool SendWithAttachment(string title, string body, string name, string address, params string[] attachments);
/// <summary>
/// 发送携带附件的邮件
/// </summary>
/// <param name="title">标题</param>
/// <param name="body">内容</param>
/// <param name="name">目标用户名</param>
/// <param name="address">目标邮箱地址</param>
/// <param name="attachments">附件</param>
/// <returns></returns>
Task<bool> SendWithAttachmentAsync(string title, string body, string name, string address, params string[] attachments);
}
2.实现
配置服务可以换成你的配置项,代码里用到的其他封装,可以自行实现,并无其他依赖。
/// <summary>
/// 邮箱服务
/// </summary>
[Component(AutofacScope = AutofacScope.SingleInstance)]
public class EMailService : IEMailService
{
/// <summary>
/// 配置服务
/// </summary>
[Autowired]
private IConfigService configService { get; set; }
/// <summary>
/// 获取发送方邮箱地址
/// </summary>
private MailboxAddress FromMailboxAddress => new MailboxAddress(Encoding.UTF8,
configService.GetCacheValue<string>("Config:System:EMail:Name"),
configService.GetCacheValue<string>("Config:System:EMail:Address"));
/// <summary>
/// 邮箱地址
/// </summary>
private string Address => configService.GetCacheValue<string>("Config:System:EMail:Address");
/// <summary>
/// 服务器
/// </summary>
private string Host => configService.GetCacheValue<string>("Config:System:EMail:Host");
/// <summary>
/// 端口
/// </summary>
private int Port => configService.GetCacheValue<int>("Config:System:EMail:Port");
/// <summary>
/// 密码
/// </summary>
private string Password => configService.GetCacheValue<string>("Config:System:EMail:Password");
/// <inheritdoc/>
public bool Send(string title, string body, string name, string address) => Send(title, body, (name, address));
/// <inheritdoc/>
public bool Send(string title, string body, params (string name, string address)[] receives)
{
if (receives == null || receives.Length < 1)
return false;
//邮箱信息
using var mimeMessage = new MimeMessage();
//邮件标题
mimeMessage.Subject = title;
//发送方
mimeMessage.From.Add(FromMailboxAddress);
//接收方
foreach (var receive in receives)
mimeMessage.To.Add(new MailboxAddress(receive.name, receive.address));
//优先级
mimeMessage.Importance = MessageImportance.High;
//邮件内容
mimeMessage.Body = new TextPart(TextFormat.Html)
{
Text = body
};
try
{
using var smtp = new SmtpClient();
smtp.MessageSent += (sender, args) =>
{
//响应事件,方便记录,args.Response
};
smtp.ServerCertificateValidationCallback = (s, c, h, e) => true;
//outlook.com需要设置为SecureSocketOptions.StartTls
if (mimeMessage.From.Any(a => ((MailboxAddress)a).Address.Contains("outlook.com")) || mimeMessage.To.Any(a => ((MailboxAddress)a).Address.Contains("outlook.com")))
smtp.Connect(Host, Port, SecureSocketOptions.StartTls);
else
smtp.Connect(Host, Port, SecureSocketOptions.Auto);
smtp.Authenticate(Address, Password);
smtp.Send(mimeMessage);
smtp.Disconnect(true);
return true;
}
catch (Exception ex)
{
LogHelper.Error($"发送EMail给[{string.Join("、", receives.Select(_ => $"{_.name}({_.address})"))}]失败,{ex.Message},{ex.StackTrace}");
return false;
}
}
/// <inheritdoc/>
public async Task<bool> SendAsync(string title, string body, string name, string address) => await SendAsync(title, body, (name, address));
/// <inheritdoc/>
public async Task<bool> SendAsync(string title, string body, params (string name, string address)[] receives)
{
if (receives == null || receives.Length < 1)
return false;
//邮箱信息
using var mimeMessage = new MimeMessage();
//邮件标题
mimeMessage.Subject = title;
//发送方
mimeMessage.From.Add(FromMailboxAddress);
//接收方
foreach (var receive in receives)
mimeMessage.To.Add(new MailboxAddress(receive.name, receive.address));
//优先级
mimeMessage.Importance = MessageImportance.High;
//邮件内容
mimeMessage.Body = new TextPart(TextFormat.Html)
{
Text = body
};
try
{
using var smtp = new SmtpClient();
smtp.MessageSent += (sender, args) =>
{
//响应事件,方便记录,args.Response
};
smtp.ServerCertificateValidationCallback = (s, c, h, e) => true;
//outlook.com需要设置为SecureSocketOptions.StartTls
if (mimeMessage.From.Any(a => ((MailboxAddress)a).Address.Contains("outlook.com")) || mimeMessage.To.Any(a => ((MailboxAddress)a).Address.Contains("outlook.com")))
smtp.Connect(Host, Port, SecureSocketOptions.StartTls);
else
smtp.Connect(Host, Port, SecureSocketOptions.Auto);
smtp.Authenticate(Address, Password);
await smtp.SendAsync(mimeMessage);
await smtp.DisconnectAsync(true);
return true;
}
catch (Exception ex)
{
LogHelper.Error($"发送EMail给[{string.Join("、", receives.Select(_ => $"{_.name}({_.address})"))}]失败,{ex.Message},{ex.StackTrace}");
return false;
}
}
/// <inheritdoc/>
public bool SendWithAttachment(string title, string body, string name, string address, params string[] attachments)
{
//邮箱信息
using var mimeMessage = new MimeMessage();
//邮件标题
mimeMessage.Subject = title;
//发送方
mimeMessage.From.Add(FromMailboxAddress);
//接收方
mimeMessage.To.Add(new MailboxAddress(name, address));
//优先级
mimeMessage.Importance = MessageImportance.High;
//邮件内容
var multipart = new Multipart("mixed")
{
new TextPart(TextFormat.Html)
{
Text = body
}
};
var attachmentFiles = new List<(bool isTemp, string filePath, FileStream file)>();
try
{
//添加邮件附件
foreach (var path in attachments)
{
var filePath = path;
var isTemp = false;
var fileName = string.Empty;
if (File.Exists(path))
{
filePath = path;
fileName = Path.GetFileName(filePath);
}
else if (path.StartsWith("http"))
{
var uri = new Uri(path);
var query = HttpUtility.ParseQueryString(uri.Query);
if (query != null && query.HasKeys())
{
fileName = query.Get("name");
if (!string.IsNullOrWhiteSpace(fileName))
fileName = $"{fileName}{Path.GetExtension(uri.AbsolutePath)}";
}
if (string.IsNullOrWhiteSpace(fileName))
fileName = Path.GetFileName(path);
var tempFolderPath = Path.Combine(AppContext.BaseDirectory, "temp");
if (!Directory.Exists(tempFolderPath))
Directory.CreateDirectory(tempFolderPath);
filePath = Path.Combine(tempFolderPath, $"{Guid.NewGuid():N}{Path.GetExtension(uri.AbsolutePath)}");
if (!HttpHelper.Download(filePath, uri.AbsoluteUri, null, null))
{
LogHelper.Error($"发送EMail失败,附件地址[{path}]无法下载");
return false;
}
isTemp = true;
}
else
{
LogHelper.Error($"发送EMail失败,附件地址[{path}]异常");
return false;
}
var fileType = MimeTypes.GetMimeType(filePath);
var contentTypeArr = fileType.Split('/');
var contentType = new ContentType(contentTypeArr[0], contentTypeArr[1]);
MimePart attachment = null;
var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
attachmentFiles.Add((isTemp, filePath, fs));
attachment = new MimePart(contentType)
{
Content = new MimeContent(fs),
ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
ContentTransferEncoding = ContentEncoding.Base64
};
//编码处理,否则中文附件会错误
var charset = "GB18030";
attachment.ContentType.Parameters.Add(charset, "name", fileName);
attachment.ContentDisposition.Parameters.Add(charset, "filename", fileName);
foreach (var param in attachment.ContentDisposition.Parameters)
param.EncodingMethod = ParameterEncodingMethod.Rfc2047;
foreach (var param in attachment.ContentType.Parameters)
param.EncodingMethod = ParameterEncodingMethod.Rfc2047;
multipart.Add(attachment);
}
//邮件实体
mimeMessage.Body = multipart;
using var smtp = new SmtpClient();
smtp.MessageSent += (sender, args) =>
{
//响应事件,方便记录,args.Response
};
smtp.ServerCertificateValidationCallback = (s, c, h, e) => true;
//outlook.com需要设置为SecureSocketOptions.StartTls
if (mimeMessage.From.Any(a => ((MailboxAddress)a).Address.Contains("outlook.com")) || mimeMessage.To.Any(a => ((MailboxAddress)a).Address.Contains("outlook.com")))
smtp.Connect(Host, Port, SecureSocketOptions.StartTls);
else
smtp.Connect(Host, Port, SecureSocketOptions.Auto);
smtp.Authenticate(Address, Password);
smtp.Send(mimeMessage);
smtp.Disconnect(true);
//文件流释放和临时文件清理
foreach (var fs in attachmentFiles)
{
fs.file.Dispose();
fs.file.Close();
if (fs.isTemp)
{
if (File.Exists(fs.filePath))
File.Delete(fs.filePath);
}
}
return true;
}
catch (Exception ex)
{
LogHelper.Error($"发送EMail给[{$"{name}({address})"}]失败,{ex.Message},{ex.StackTrace}");
return false;
}
}
/// <inheritdoc/>
public async Task<bool> SendWithAttachmentAsync(string title, string body, string name, string address, params string[] attachments)
{
//邮箱信息
using var mimeMessage = new MimeMessage();
//邮件标题
mimeMessage.Subject = title;
//发送方
mimeMessage.From.Add(FromMailboxAddress);
//接收方
mimeMessage.To.Add(new MailboxAddress(name, address));
//优先级
mimeMessage.Importance = MessageImportance.High;
//邮件内容
var multipart = new Multipart("mixed")
{
new TextPart(TextFormat.Html)
{
Text = body
}
};
var attachmentFiles = new List<(bool isTemp, string filePath, FileStream file)>();
try
{
//添加邮件附件
foreach (var path in attachments)
{
var filePath = path;
var isTemp = false;
var fileName = string.Empty;
if (File.Exists(path))
{
filePath = path;
fileName = Path.GetFileName(filePath);
}
else if (path.StartsWith("http"))
{
var uri = new Uri(path);
var query = HttpUtility.ParseQueryString(uri.Query);
if (query != null && query.HasKeys())
{
fileName = query.Get("name");
if (!string.IsNullOrWhiteSpace(fileName))
fileName = $"{fileName}{Path.GetExtension(uri.AbsolutePath)}";
}
if (string.IsNullOrWhiteSpace(fileName))
fileName = Path.GetFileName(path);
var tempFolderPath = Path.Combine(AppContext.BaseDirectory, "temp");
if (!Directory.Exists(tempFolderPath))
Directory.CreateDirectory(tempFolderPath);
filePath = Path.Combine(tempFolderPath, $"{Guid.NewGuid():N}{Path.GetExtension(uri.AbsolutePath)}");
if (!HttpHelper.Download(filePath, uri.AbsoluteUri, null, null))
{
LogHelper.Error($"发送EMail失败,附件地址[{path}]无法下载");
return false;
}
isTemp = true;
}
else
{
LogHelper.Error($"发送EMail失败,附件地址[{path}]异常");
return false;
}
var fileType = MimeTypes.GetMimeType(filePath);
var contentTypeArr = fileType.Split('/');
var contentType = new ContentType(contentTypeArr[0], contentTypeArr[1]);
MimePart attachment = null;
var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
attachmentFiles.Add((isTemp, filePath, fs));
attachment = new MimePart(contentType)
{
Content = new MimeContent(fs),
ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
ContentTransferEncoding = ContentEncoding.Base64
};
//编码处理,否则中文附件会错误
var charset = "GB18030";
attachment.ContentType.Parameters.Add(charset, "name", fileName);
attachment.ContentDisposition.Parameters.Add(charset, "filename", fileName);
foreach (var param in attachment.ContentDisposition.Parameters)
param.EncodingMethod = ParameterEncodingMethod.Rfc2047;
foreach (var param in attachment.ContentType.Parameters)
param.EncodingMethod = ParameterEncodingMethod.Rfc2047;
multipart.Add(attachment);
}
//邮件实体
mimeMessage.Body = multipart;
using var smtp = new SmtpClient();
smtp.MessageSent += (sender, args) =>
{
//响应事件,方便记录,args.Response
};
smtp.ServerCertificateValidationCallback = (s, c, h, e) => true;
//outlook.com需要设置为SecureSocketOptions.StartTls
if (mimeMessage.From.Any(a => ((MailboxAddress)a).Address.Contains("outlook.com")) || mimeMessage.To.Any(a => ((MailboxAddress)a).Address.Contains("outlook.com")))
smtp.Connect(Host, Port, SecureSocketOptions.StartTls);
else
smtp.Connect(Host, Port, SecureSocketOptions.Auto);
smtp.Authenticate(Address, Password);
await smtp.SendAsync(mimeMessage);
await smtp.DisconnectAsync(true);
//文件流释放和临时文件清理
foreach (var fs in attachmentFiles)
{
fs.file.Dispose();
fs.file.Close();
if (fs.isTemp)
{
if (File.Exists(fs.filePath))
File.Delete(fs.filePath);
}
}
return true;
}
catch (Exception ex)
{
LogHelper.Error($"发送EMail给[{$"{name}({address})"}]失败,{ex.Message},{ex.StackTrace}");
return false;
}
}
}
- 0
- 0
-
分享