C#如何使用SMTP协议发送带附件的邮件?附完整工具类实现

来源:站长平台作者:长沙网站建设头衔:草根站长
导读:本期聚焦于小伙伴创作的《C#如何使用SMTP协议发送带附件的邮件?附完整工具类实现》,敬请观看详情。在后台服务或业务系统中集成邮件通知时,直接拼HTTP接口往往受限于第三方配额与稳定性。利用.NET内置的SmtpClient与MailMessage,可在不引入额外依赖的情况下完成文本、HTML及多附件投递。需要注意,SmtpClient自.NET 5起被标记为过时,官方推荐MailKit,但旧项目仍广泛沿用该系统类库。本文梳理了主机配置、鉴权方式、附件流式写入与异常重试要点,并给出可复用的静态工具类。通过合理设置Timeout与DeliveryMethod,能避免线程阻塞并提升投递成功率,同时应注意收件人、抄送集合的规范化拼接,防止格式错误导致服务端拒收。

在C#项目中实现邮件发送,本质上是通过SMTP协议与邮件服务器建立会话,并依照RFC标准提交发件人、收件人、正文与附件数据。.NET Framework及早期.NET Core版本提供了System.Net.Mail命名空间下的SmtpClient和MailMessage类型,开发者无需关心底层的Socket通信,即可完成一封符合协议规范的电子邮件构造与投递。下面我们先看一个基础的工具类结构。

C#如何使用SMTP协议发送带附件的邮件?附完整工具类实现

一、SMTP邮件发送的核心类说明

MailMessage负责描述一封邮件的所有内容,包括发件人地址、收件人列表、主题、正文以及附件集合。它的From属性接收一个MailAddress实例,To属性是一个MailAddressCollection,可以调用Add方法添加多个收件人。如果业务需要抄送或密送,则分别对应CC和Bcc属性。正文部分可通过Body属性设置纯文本,或将IsBodyHtml设为true来发送HTML格式的邮件。

SmtpClient则是真正执行发送动作的客户端。它需要配置Host(SMTP服务器地址)、Port(常用25、465或587)、Credentials(登录凭据)以及EnableSsl(是否启用加密连接)。在旧版.NET中,调用Send方法会阻塞当前线程,因此工具类通常提供异步的SendMailAsync以避免接口超时。以下代码展示了这两个类的基本关系:

using System;
using System.Net;
using System.Net.Mail;

// 构造邮件内容
MailMessage msg = new MailMessage();
msg.From = new MailAddress("notify@ipipp.com", "系统通知");
msg.To.Add("user@ipipp.com");
msg.Subject = "测试邮件";
msg.Body = "这是一封通过C#发送的测试邮件";
msg.IsBodyHtml = false;

// 配置SMTP客户端
SmtpClient client = new SmtpClient();
client.Host = "smtp.ipipp.com";
client.Port = 587;
client.EnableSsl = true;
client.Credentials = new NetworkCredential("notify@ipipp.com", "your_password");

二、带附件发送的详细实现

附件在MailMessage中以Attachment类表示,最常见的方式是从本地文件路径构造,例如new Attachment("d:/report.pdf")。但在Web或后台服务中,文件可能以流的形式存在,此时应使用接收Stream和MediaTypeNames的构造函数,避免产生临时文件。添加多个附件只需多次调用Attachments.Add方法,系统会自动在邮件体中生成multipart/mixed结构。

需要注意,Attachment默认会根据文件扩展名推断MIME类型,若发送的是自定义格式,应显式指定ContentType。另外,附件名称包含中文时,某些旧邮件服务器会出现乱码,可通过将NameEncoding设为UTF8来规避。下面的工具类方法演示了如何从字节数组添加附件:

public static void AddAttachmentFromBytes(MailMessage msg, string name, byte[] data)
{
    using (var stream = new System.IO.MemoryStream(data))
    {
        Attachment att = new Attachment(stream, name, System.Net.Mime.MediaTypeNames.Application.Octet);
        att.NameEncoding = System.Text.Encoding.UTF8;
        msg.Attachments.Add(att);
    }
}

三、可复用的邮件发送工具类

为了提高代码可维护性,我们将上述逻辑封装为一个静态工具类,支持多收件人、多附件与异步发送。工具类内部统一读取配置项的服务器信息,并在发送失败时抛出包含状态码的异常,方便上层业务记录日志。对于大附件,建议设置SmtpClient.Timeout为较长值,例如30000毫秒,防止网络抖动引发中途断开。

下面的完整示例展示了如何在一个方法中完成收件人拆分、正文渲染与附件绑定。实际项目中可将SMTP账号信息放入appsettings.json,通过IConfiguration注入,但为保持示例简洁,这里采用硬编码演示核心流程。此工具类也演示了如何使用MailPriority设置邮件优先级。

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Mail;
using System.Threading.Tasks;

public static class EmailHelper
{
    private static string SmtpHost = "smtp.ipipp.com";
    private static int SmtpPort = 587;
    private static string FromAddr = "notify@ipipp.com";
    private static string FromPwd = "your_password";

    public static async Task SendAsync(List<string> toList, string subject, string body, bool isHtml, List<Tuple<string, byte[]>> attachments)
    {
        using (MailMessage msg = new MailMessage())
        {
            msg.From = new MailAddress(FromAddr, "自动通知");
            foreach (var item in toList)
            {
                if (!string.IsNullOrWhiteSpace(item))
                    msg.To.Add(item.Trim());
            }
            msg.Subject = subject;
            msg.Body = body;
            msg.IsBodyHtml = isHtml;
            msg.Priority = MailPriority.Normal;

            if (attachments != null)
            {
                foreach (var file in attachments)
                {
                    var stream = new System.IO.MemoryStream(file.Item2);
                    Attachment att = new Attachment(stream, file.Item1, System.Net.Mime.MediaTypeNames.Application.Octet);
                    att.NameEncoding = System.Text.Encoding.UTF8;
                    msg.Attachments.Add(att);
                }
            }

            using (SmtpClient client = new SmtpClient())
            {
                client.Host = SmtpHost;
                client.Port = SmtpPort;
                client.EnableSsl = true;
                client.Timeout = 30000;
                client.Credentials = new NetworkCredential(FromAddr, FromPwd);
                await client.SendMailAsync(msg);
            }
        }
    }
}

四、常见错误与避坑建议

第一个常见误区是认为SmtpClient是线程安全的。实际上官方文档明确指出一个SmtpClient实例不应被多个线程同时使用,因此在上述工具类中我们每次发送都new一个新的SmtpClient,并在using块中释放。第二个误区是忽略防火墙或云服务商对25端口的封锁,在云服务器上应优先使用587或465并开启SSL。

另一个容易被忽视的点是Dispose附件流。MailMessage在Dispose时会自动释放Attachment中的流,但如果我们提前关闭了外部传入的流,就会导致发送时读取失败。因此工具类内部采用MemoryStream并在using范围内由MailMessage接管生命周期,是最稳妥的做法。若发送失败,应捕获SmtpException并区分StatusCode,例如MailboxUnavailable代表收件人地址无效,可加入重试白名单逻辑。

五、总结

通过System.Net.Mail下的SmtpClient与MailMessage,C#开发者可以快速搭建起邮件发送能力,配合静态工具类能让业务代码保持整洁。虽然新项目更推荐MailKit以获得更好的协议兼容性与安全性,但理解原生SMTP发送机制仍有助于排查投递故障。只要注意附件编码、超时设置与凭据保护,带附件的邮件发送就能稳定服务于通知、报表推送等场景。

C#_SMTP邮件发送附件处理修改时间:2026-08-05 04:27:31

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。