在C#中发送邮件,最经典也是最直接的方式就是使用.NET Framework自带的SmtpClient类。它封装了SMTP协议的交互细节,开发者只需要配置服务器地址、端口、账号密码,再组装好一封MailMessage对象,几行代码就能把邮件发出去。这篇文章会从最基础的同步发送讲起,逐步扩展到附件、HTML正文、异步发送等常用功能,最后把实际项目中容易踩到的坑逐一说明,看完基本可以覆盖绝大多数发信需求。

一、SmtpClient发送邮件的基本用法
先来看最简单的例子。发送邮件主要涉及两个类:一个是MailMessage,负责描述邮件本身,包括发件人、收件人、主题、正文等;另一个是SmtpClient,负责和SMTP服务器通信,把邮件真正投递出去。这两个类都位于System.Net.Mail命名空间下,使用前记得先引入。
using System;
using System.Net;
using System.Net.Mail;
class Program
{
static void Main()
{
// 1. 构造邮件对象
MailMessage mail = new MailMessage();
mail.From = new MailAddress("sender@ipipp.com", "系统通知");
mail.To.Add("receiver@example.net");
mail.Subject = "测试邮件";
mail.Body = "这是一封来自C#程序的测试邮件。";
mail.SubjectEncoding = System.Text.Encoding.UTF8;
mail.BodyEncoding = System.Text.Encoding.UTF8;
// 2. 配置SMTP客户端
SmtpClient smtp = new SmtpClient("smtp.ipipp.com", 25);
smtp.Credentials = new NetworkCredential("sender@ipipp.com", "你的密码或授权码");
// 3. 发送
smtp.Send(mail);
Console.WriteLine("发送成功");
}
}
这段代码里有几个点值得注意。首先是SubjectEncoding和BodyEncoding,如果收件内容包含中文,强烈建议显式设置为UTF-8,否则在某些客户端上可能出现乱码。其次是Credentials属性,它接受一个NetworkCredential对象,用来完成SMTP身份认证。现在大多数邮件服务商(比如QQ邮箱、163邮箱)都不再允许直接使用登录密码,而是要求使用授权码,在邮箱设置里开启SMTP服务后可以获取。
关于端口的选择,这里多说一句。25端口是SMTP的传统端口,明文传输,很多云服务器厂商出于反垃圾邮件考虑已经封禁了它。465端口是隐式SSL端口,但SmtpClient对这个端口的支持并不好,经常出现连接超时。实际项目中最推荐的是587端口,配合EnableSsl = true使用显式SSL(STARTTLS),兼容性和安全性都比较理想。
二、发送HTML正文、附件与多收件人
实际业务中的邮件往往不是纯文本那么简单。营销通知需要漂亮的HTML排版,订单确认需要附带PDF账单,告警邮件可能要同时发给好几个运维人员。这些需求通过MailMessage的相关属性都能轻松实现。
MailMessage mail = new MailMessage();
mail.From = new MailAddress("sender@ipipp.com", "订单系统");
mail.To.Add("user1@example.net"); // 收件人,可以添加多个
mail.CC.Add("user2@example.net"); // 抄送
mail.Bcc.Add("boss@example.net"); // 密送
mail.Subject = "您的订单已发货";
// HTML正文
mail.IsBodyHtml = true;
mail.Body = "<h2>订单发货通知</h2>" +
"<p>您的订单 <strong>SO-20240001</strong> 已发出,预计3天送达。</p>";
// 添加附件
Attachment att = new Attachment(@"D:\orders\SO-20240001.pdf",
System.Net.Mime.ContentTypeNames.Application.Pdf);
att.Name = "发货单.pdf";
mail.Attachments.Add(att);
using (SmtpClient smtp = new SmtpClient("smtp.ipipp.com", 587))
{
smtp.EnableSsl = true;
smtp.Credentials = new NetworkCredential("sender@ipipp.com", "授权码");
smtp.Send(mail);
}
IsBodyHtml设为true之后,Body属性中的内容会被当作HTML渲染。如果同一封邮件既想要HTML版本又想兼容不支持HTML的客户端,可以使用AlternateView创建纯文本备用视图,这是比较规范的做法。附件部分要注意,Attachment对象持有文件句柄,发送完毕后最好及时释放,用mail.Dispose()或者对附件单独调用Dispose都可以。
To、CC、Bcc三个集合都支持添加多个地址,直接连续调用Add方法即可。区别在于抄送人的地址对所有收件人可见,而密送人的地址是隐藏的。如果收件人列表很长,建议分批发送或者把地址放在Bcc中,一次性发送几十上百个地址很容易触发服务商的反垃圾机制。
三、异步发送与常见报错排查
在Web项目或者接口服务中发送邮件时,同步的Send方法会阻塞当前线程,SMTP服务器响应慢的时候整个请求都会被拖住。这时候应该使用异步方法SendAsync,或者基于任务的写法把发送动作丢到后台。
public static async Task SendMailAsync(string to, string subject, string body)
{
using (MailMessage mail = new MailMessage())
{
mail.From = new MailAddress("sender@ipipp.com", "系统通知");
mail.To.Add(to);
mail.Subject = subject;
mail.Body = body;
mail.SubjectEncoding = System.Text.Encoding.UTF8;
mail.BodyEncoding = System.Text.Encoding.UTF8;
using (SmtpClient smtp = new SmtpClient("smtp.ipipp.com", 587))
{
smtp.EnableSsl = true;
smtp.Credentials = new NetworkCredential("sender@ipipp.com", "授权码");
await smtp.SendMailAsync(mail);
}
}
}
SendMailAsync返回Task对象,配合await使用非常自然,也不会像老的SendAsync那样需要处理SendCompleted事件回调。有一点需要留意:SmtpClient本身不是线程安全的,不要让多个线程共用同一个实例并发发送,正确做法是每次发送都创建新的对象,或者用完即释放。
最后整理几个高频报错。第一类是认证失败,报错信息通常包含authentication failed或者535代码,绝大多数原因是用了登录密码而不是授权码,或者授权码输入有多余空格。第二类是连接超时,优先检查端口是否正确、防火墙是否放行,尤其是用465端口连接失败时,换成587加EnableSsl往往就解决了。第三类是发送成功但收不到,多半是进了垃圾箱,可以从几个方面改善:让发件域名配置SPF、DKIM记录,邮件标题和正文避免大量敏感营销词汇,控制发送频率。第四类是中文乱码,前面提到的编码设置没做的话就容易出现,把SubjectEncoding和BodyEncoding都设为UTF8即可。
另外提一下,微软官方已经将SmtpClient标记为不推荐在新项目中使用,建议的替代方案是MailKit这个开源库。它对现代SMTP特性支持更完整,465隐式SSL没有兼容问题,异步模型也更完善。不过SmtpClient依然能用,对于简单的发信场景或者老项目维护,它的上手成本是最低的。如果项目对邮件功能要求不高,继续使用SmtpClient完全没问题;如果涉及复杂的邮件处理需求,可以考虑迁移到MailKit。
C#发邮件SmtpClientSMTP修改时间:2026-09-16 11:40:36