18. 邮件发送
提示
邮件处理采用 MailKit 组件框架。MailKit 是一个开源的 C# 邮件处理库,用于在应用程序中发送和接收电子邮件。它提供了一个强大且易于使用的 API,支持多种邮件协议,包括 SMTP、POP3、和 IMAP。
SMTP协议
SMTP是一种提供可靠且有效的电子邮件传输的协议。SMTP是建立在FTP文件传输服务上的一种邮件服务,主要用于系统之间的邮件信息传递,并提供有关来信的通知。SMTP独立于特定的传输子系统,且只需要可靠有序的数据流信道支持,SMTP的重要特性之一是其能跨越网络传输邮件,即“SMTP邮件中继”。使用SMTP,可实现相同网络处理进程之间的邮件传输,也可通过中继器或网关实现某处理进程与其他网络之间的邮件传输。
特性与优势 描述 多协议支持 支持 SMTP、POP3、IMAP 等多种邮件协议。 异步操作 使用异步编程模型,提高性能和响应性。 附件处理 提供灵活的附件处理功能,支持添加、读取和保存邮件附件。 SSL/TLS 支持 支持安全套接字层(SSL)和传输层安全性(TLS),确保邮件的安全传输。 容错处理 提供容错处理机制,处理网络或协议错误,确保稳定的邮件通信。 丰富的 API 提供丰富的 API,方便开发人员访问邮件的各个方面,包括主题、发件人、收件人等。 跨平台 MailKit 是一个跨平台的邮件处理库,可在多个操作系统上运行,包括 Windows、Linux 和 macOS。
| 邮箱类型 | POP3服务器 | SMTP服务器 | 
|---|---|---|
| 【QQ邮箱】 | pop.qq.com(端口:110) | smtp.qq.com(端口:25) | 
| 【sina.com】 | pop3.sina.com.cn(端口:110) | smtp.sina.com.cn(端口:25) | 
| 【sinaVIP】 | pop3.vip.sina.com (端口:110) | smtp.vip.sina.com (端口:25) | 
| 【sohu.com】 | pop3.sohu.com(端口:110) | smtp.sohu.com(端口:25) | 
| 【126邮箱】 | pop.126.com(端口:110) | smtp.126.com(端口:25) | 
| 【139邮箱】 | POP.139.com(端口:110) | SMTP.139.com(端口:25) | 
| 【163.com】 | pop.163.com(端口:110) | smtp.163.com(端口:25) | 
| 【QQ企业邮箱】 | pop.exmail.qq.com(SSL启用 端口:995) | smtp.exmail.qq.com(SSL启用 端口:587/465) | 
| 【yahoo.com】 | pop.mail.yahoo.com | smtp.mail.yahoo.com | 
| 【yahoo.com.cn】 | pop.mail.yahoo.com.cn(端口:995) | smtp.mail.yahoo.com.cn(端口:587) | 
| 【HotMail】 | pop3.live.com(端口:995) | smtp.live.com(端口:587) | 
| 【 Gmail】 | pop.gmail.com(SSL启用端口:995) | smtp.gmail.com(SSL启用 端口:587) | 
| 【263.net】 | pop3.263.net(端口:110) | smtp.263.net(端口:25) | 
| 【263.net.cn】 | pop.263.net.cn(端口:110) | smtp.263.net.cn(端口:25) | 
| 【21cn.com】 | pop.21cn.com(端口:110) | smtp.21cn.com(端口:25) | 
| 【Foxmail】 | POP.foxmail.com(端口:110) | SMTP.foxmail.com(端口:25) | 
| 【china.com】 | pop.china.com(端口:110) | smtp.china.com(端口:25) | 
| 【tom.com】 | pop.tom.com(端口:110) | smtp.tom.com(端口:25) | 
提示
Admin.NET 全局异常日志默认启动发送到指定邮箱。开关由系统参数配置关键字 CommonConst.SysErrorMail 来控制。
配置文件
邮箱配置文件 Configuration/Email.json
{
  "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
  "Email": {
    "Host": "smtp.163.com", // 主机
    "Port": 465, // 端口 465、994、25
    "EnableSsl": true, // 启用SSL
    "DefaultFromEmail": "xxx@163.com", // 默认发件者邮箱
    "DefaultToEmail": "xxx@qq.com", // 默认接收人邮箱
    "UserName": "xxx@163.com", // 邮箱账号
    "Password": "", // 邮箱授权码
    "DefaultFromName": "Admin.NET 通用权限开发平台" // 默认邮件标题
  }
}系统实现
public class SysEmailService : IDynamicApiController, ITransient
{
    private readonly EmailOptions _emailOptions;
    public SysEmailService(IOptions<EmailOptions> emailOptions)
    {
        _emailOptions = emailOptions.Value;
    }
    /// <summary>
    /// 发送邮件 📧
    /// </summary>
    /// <param name="content"></param>
    /// <param name="title"></param>
    /// <returns></returns>
    [DisplayName("发送邮件")]
    public async Task SendEmail([Required] string content, string title = "Admin.NET 系统邮件")
    {
        var message = new MimeMessage();
        message.From.Add(new MailboxAddress(_emailOptions.DefaultFromEmail, _emailOptions.DefaultFromEmail));
        message.To.Add(new MailboxAddress(_emailOptions.DefaultToEmail, _emailOptions.DefaultToEmail));
        message.Subject = title;
        message.Body = new TextPart("html")
        {
            Text = content
        };
        using var client = new SmtpClient();
        client.Connect(_emailOptions.Host, _emailOptions.Port, _emailOptions.EnableSsl);
        client.Authenticate(_emailOptions.UserName, _emailOptions.Password);
        client.Send(message);
        client.Disconnect(true);
        await Task.CompletedTask;
    }
}使用方式
首先注入邮件服务 SysEmailService,然后调用方法 SendEmail 即可,邮件内容默认 HTML
                        格式。建议发送邮件、短信等消息提醒时采用事件总线模式,不阻塞主线程运行。
邮件模板
[EventSubscribe("Mail:SendOrderMail")]
public async Task SendOrderMail(EventHandlerExecutingContext context)
{
    // var orderId = (long)context.Source.Payload; // 从事件总线传过来的值
    var mailTempPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Temp\\ErrorMail.tp");
    var mailTemp = File.ReadAllText(mailTempPath);
    var mail = await _serviceScope.ServiceProvider.GetRequiredService<IViewEngine>().RunCompileFromCachedAsync(mailTemp, 数据内容);
    var title = "Admin.NET 系统异常";
    await _serviceScope.ServiceProvider.GetRequiredService<SysEmailService>().SendEmail(mail, title);
}下面是一个 HTML 邮件模板,直接采用视图模板引擎 IViewEngine 自动替换模板数据。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>订单邮件模板</title>
    <style>
        .byTable {
            width: 100%;
            border-collapse: collapse;
            padding: 2px;
        }
        .byTable, .byTable tr th, .byTable tr td {
            border: 1px solid black;
            padding: 5px 15px;
            font-size: 16px;
            }
        .tdbg1 {
            background-color: #EEEEEE;
            font-weight: 700;
        }
        .tdbg2 {
            background-color: #EEEEEE;
        }
        .tdbg3 {
            font-weight: 700;
        }
    </style>
</head>
<body>
    <h4>订单详情</h4>
    <table class="byTable">
        <tr>
            <td class="tdbg1">订单号</td>
            <td class="tdbg2">@Model.OrderId</td>
            <td class="tdbg1">销售商单号</td>
            <td class="tdbg2">@Model.SellerOrderId</td>
            <td class="tdbg1">供应商单号</td>
            <td class="tdbg2">@Model.SupplierOrderId</td>
        </tr>
        <tr>
            <td class="tdbg1">订单时间</td>
            <td class="tdbg2">@Model.OrderTime</td>
            <td class="tdbg1">入住日期</td>
            <td class="tdbg2">@Model.CheckInTime</td>
            <td class="tdbg1">离店日期</td>
            <td class="tdbg2">@Model.CheckOutTime</td>
        </tr>
        <tr>
            <td class="tdbg1">预定人员</td>
            <td class="tdbg2">@Model.Booker</td>
            <td class="tdbg1">手机号码</td>
            <td class="tdbg2">@Model.Phone</td>
            <td class="tdbg1">房间数量</td>
            <td class="tdbg2">@Model.Rooms</td>
        </tr>
        <tr></tr>
        <tr>
            <td class="tdbg3">销售商名称</td>
            <td>@Model.SellerName</td>
            <td class="tdbg3">基础酒店名称</td>
            <td>@Model.BaseHotelName</td>
            <td class="tdbg3">基础房型编码</td>
            <td>@Model.BaseRoomCode</td>
        </tr>
        <tr>
            <td class="tdbg3">销售商酒店地址</td>
            <td colspan="5">@Model.SellerHotelAddress</td>
        </tr>
        <tr>
            <td class="tdbg3">供应商名称</td>
            <td>@Model.SupplierName</td>
            <td class="tdbg3">供应商酒店名称</td>
            <td>@Model.SupplierHotelName</td>
            <td class="tdbg3">供应商房型编码</td>
            <td>@Model.SupplierRoomCode</td>
        </tr>
        <tr>
            <td class="tdbg3">供应商酒店地址</td>
            <td colspan="5">@Model.SupplierHotelAddress</td>
        </tr>
        <tr></tr>
        <tr>
            <td class="tdbg3">供应商餐饮类型</td>
            <td>@Model.SupplierBreakfastBoardCode</td>
            <td class="tdbg3">供应商餐饮数量</td>
            <td>@Model.SupplierBreakfastCount</td>
            <td></td>
            <td></td>
        </tr>
    </table>
</body>
</html>