C#邮件发送问题(一)
邮件发送需考虑很多因素,包括发送邮件客户端(一般编码实现),发送和接收邮件服务器设置等。如果使用第三方邮件服务器作为发送服务器,就需要考虑该服务器的发送限制,(如发送邮件时间间隔,单位时间内发送邮件数量,是否使用安全连接SSL),同时无论使用第三方还是自己的邮件服务器都还需要考虑接收邮件服务器的限制。为理清思路,下面我们简单回顾电子邮件系统的基本网络结构和邮件发送接收流程。
一、电子邮件系统的基本网络结构
如下图:
邮件发送接收一般经过以下几个节点:
- 发送邮件客户端(Mail User Agent, MUA) : Formail, Outlook, Webmail, C# Code, Java Code, etc.
- 发送邮件服务器(Mail Transfer Agent, MTA) : hMailServer, Exchange, TurboMail, etc.
- 接收邮件服务器(Mail Transfer Agent, MTA)
- 接收邮件客户端(Mail User Agent, MUA)
发送过程中客户端与服务器及服务器之间使用SMTP协议,在接收过程中客户端与服务端之间使用POP3或IMAP(POP3的替代协议,支持邮件摘要显示和脱机操作)。邮件发送可简单认为是一种文件传输,但与FTP实时文件传输不同,各邮件服务器会保存邮件文件本身,直至被下一个邮件服务器或客户端接收,类似异步与同步的差别。
由上可知,为顺利发送和接受邮件,客户端设置或编码需要严格适应邮件服务器的要求。对于发送邮件需明确:SMTP服务器地址和端口(默认端口25),是否使用安全连接(SSL),验证凭据(用户和密码),及更加细节的邮件格式,邮件编码方式等;对于接收邮件需明确:POP3或IMAP服务器地址和端口(POP3默认端口110,IMAP默认端口143),是否使用安全连接(SSL),验证凭据(用户和密码)
二、C#下发送邮件组件及测试
C#下发送邮件的组件使用较为普遍的有以下三个:System.Net.Mail, OpenSmtp, LumiSoft.Net。下面我们就分别对他们进行测试。
发送邮件至少需要发送邮件服务器信息和邮件信息,因此我们建立Host和Mail两个配置类。
public class ConfigHost
{
public string Server { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool EnableSsl { get; set; }
} public class ConfigMail
{
public string From { get; set; }
public string[] To { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public string[] Attachments { get; set; }
public string[] Resources { get; set; }
}
同时定义一个统一的接口ISendMail,以方便测试和比较。
public interface ISendMail
{
void CreateHost(ConfigHost host);
void CreateMail(ConfigMail mail);
void CreateMultiMail(ConfigMail mail);
void SendMail();
}
1、使用System.Net.Mail
System.Net.Mail属于.Net Framework 的一部分,.Net2.0以后可以使用这个组件。
using System.Net.Mail;
public class UseNetMail : ISendMail
{
private MailMessage Mail { get; set; }
private SmtpClient Host { get; set; } public void CreateHost(ConfigHost host)
{
Host = new SmtpClient(host.Server, host.Port);
Host.Credentials = new System.Net.NetworkCredential(host.Username, host.Password);
Host.EnableSsl = host.EnableSsl;
} public void CreateMail(ConfigMail mail)
{
Mail = new MailMessage();
Mail.From = new MailAddress(mail.From); foreach (var t in mail.To)
Mail.To.Add(t); Mail.Subject = mail.Subject;
Mail.Body = mail.Body;
Mail.IsBodyHtml = true;
Mail.BodyEncoding = System.Text.Encoding.UTF8;
} public void CreateMultiMail(ConfigMail mail)
{
CreateMail(mail); Mail.AlternateViews.Add(AlternateView.CreateAlternateViewFromString("If you see this message, it means that your mail client does not support html.", Encoding.UTF8, "text/plain")); var html = AlternateView.CreateAlternateViewFromString(mail.Body, Encoding.UTF8, "text/html");
foreach (string resource in mail.Resources)
{
var image = new LinkedResource(resource, "image/jpeg");
image.ContentId = Convert.ToBase64String(Encoding.Default.GetBytes(Path.GetFileName(resource)));
html.LinkedResources.Add(image);
}
Mail.AlternateViews.Add(html); foreach (var attachment in mail.Attachments)
{
Mail.Attachments.Add(new Attachment(attachment));
}
} public void SendMail()
{
if (Host != null && Mail != null)
Host.Send(Mail);
else
throw new Exception("These is not a host to send mail or there is not a mail need to be sent.");
}
}
using OpenSmtp.Mail;
public class UseOpenSmtp : ISendMail
{
private MailMessage Mail { get; set; }
private Smtp Host { get; set; } public void CreateHost(ConfigHost host)
{
Host = new Smtp(host.Server, host.Username, host.Password, host.Port);
} public void CreateMail(ConfigMail mail)
{
Mail = new MailMessage();
Mail.From = new EmailAddress(mail.From);
foreach (var t in mail.To)
Mail.AddRecipient(t, AddressType.To); Mail.HtmlBody = mail.Body;
Mail.Subject = mail.Subject;
Mail.Charset = "UTF-8";
} public void CreateMultiMail(ConfigMail mail)
{
CreateMail(mail);
foreach (var attachment in mail.Attachments)
{
Mail.AddAttachment(attachment);
}
foreach (var resource in mail.Resources)
{
Mail.AddImage(resource, Convert.ToBase64String(Encoding.Default.GetBytes(Path.GetFileName(resource))));
}
} public void SendMail()
{
if (Host != null && Mail != null)
Host.SendMail(Mail);
else
throw new Exception("These is not a host to send mail or there is not a mail need to be sent.");
}
3、使用LumiSoft.Net
LumiSoft.Net是非常强大的开源组件,不仅仅发送邮件,同样也可用于接收邮件,是个人认为最好的开源组件了。在这里可以详细了解LumiSoft.Net组件的命名空间,也可以在这里下载其源码和样例。
using LumiSoft.Net.SMTP.Client;
using LumiSoft.Net.AUTH;
using LumiSoft.Net.Mail;
using LumiSoft.Net.MIME;
public class UseLumiSoft : ISendMail
{
private SMTP_Client Host { get; set; }
private Mail_Message Mail { get; set; } public void CreateHost(ConfigHost host)
{
Host = new SMTP_Client();
Host.Connect(host.Server, host.Port, host.EnableSsl);
Host.EhloHelo(host.Server);
Host.Auth(Host.AuthGetStrongestMethod(host.Username, host.Password));
} public void CreateMail(ConfigMail mail)
{
Mail = new Mail_Message();
Mail.Subject = mail.Subject;
Mail.From = new Mail_t_MailboxList();
Mail.From.Add(new Mail_t_Mailbox(mail.From, mail.From));
Mail.To = new Mail_t_AddressList();
foreach (var to in mail.To)
{
Mail.To.Add(new Mail_t_Mailbox(to, to));
}
var body = new MIME_b_Text(MIME_MediaTypes.Text.html);
Mail.Body = body; //Need to be assigned first or will throw "Body must be bounded to some entity first" exception.
body.SetText(MIME_TransferEncodings.Base64, Encoding.UTF8, mail.Body);
} public void CreateMultiMail(ConfigMail mail)
{
CreateMail(mail); var contentTypeMixed = new MIME_h_ContentType(MIME_MediaTypes.Multipart.mixed);
contentTypeMixed.Param_Boundary = Guid.NewGuid().ToString().Replace("-", "_");
var multipartMixed = new MIME_b_MultipartMixed(contentTypeMixed);
Mail.Body = multipartMixed; //Create a entity to hold multipart/alternative body
var entityAlternative = new MIME_Entity();
var contentTypeAlternative = new MIME_h_ContentType(MIME_MediaTypes.Multipart.alternative);
contentTypeAlternative.Param_Boundary = Guid.NewGuid().ToString().Replace("-", "_");
var multipartAlternative = new MIME_b_MultipartAlternative(contentTypeAlternative);
entityAlternative.Body = multipartAlternative;
multipartMixed.BodyParts.Add(entityAlternative); var entityTextPlain = new MIME_Entity();
var plain = new MIME_b_Text(MIME_MediaTypes.Text.plain);
entityTextPlain.Body = plain;
plain.SetText(MIME_TransferEncodings.Base64, Encoding.UTF8, "If you see this message, it means that your mail client does not support html.");
multipartAlternative.BodyParts.Add(entityTextPlain); var entityTextHtml = new MIME_Entity();
var html = new MIME_b_Text(MIME_MediaTypes.Text.html);
entityTextHtml.Body = html;
html.SetText(MIME_TransferEncodings.Base64, Encoding.UTF8, mail.Body);
multipartAlternative.BodyParts.Add(entityTextHtml); foreach (string attachment in mail.Attachments)
{
multipartMixed.BodyParts.Add(Mail_Message.CreateAttachment(attachment));
} foreach (string resource in mail.Resources)
{
var entity = new MIME_Entity();
entity.ContentDisposition = new MIME_h_ContentDisposition(MIME_DispositionTypes.Inline);
entity.ContentID = Convert.ToBase64String(Encoding.Default.GetBytes(Path.GetFileName(resource))); //eg.<img src="cid:ContentID"/>
var image = new MIME_b_Image(MIME_MediaTypes.Image.jpeg);
entity.Body = image;
image.SetDataFromFile(resource, MIME_TransferEncodings.Base64);
multipartMixed.BodyParts.Add(entity);
}
} public void SendMail()
{
if (Host != null && Mail != null)
{
foreach (Mail_t_Mailbox from in Mail.From.ToArray())
{
Host.MailFrom(from.Address, -);
}
foreach (Mail_t_Mailbox to in Mail.To)
{
Host.RcptTo(to.Address);
}
using (var stream = new MemoryStream())
{
Mail.ToStream(stream, new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.Q, Encoding.UTF8), Encoding.UTF8);
stream.Position = ;//Need to be reset to 0, otherwise nothing will be sent;
Host.SendMessage(stream);
Host.Disconnect();
}
}
else
throw new Exception("These is not a host to send mail or there is not a mail need to be sent.");
}
}
阅读LumiSoft.Net的源代码,可以看到LumiSoft.Net编程严格遵循了RFC(Request For Comments)定义的协议规范。通过阅读这些源码对于了解RFC和其中关于邮件网络协议规范也是非常有帮助的。如果想查阅RFC文档可以通过这个链接。
在上面的代码中MIME_MediaTypes类,MIME_TransferEncodings类和Encoding类(System.Text.Encoding)都是或类似于枚举,设置了邮件内容的编码方式或解析方式,这个几个类从根本上决定了邮件的正常传输和显示。MIME_TransferEncodings类设置了文件传输编码,决定邮件头中的Content-Transfer-Encoding字段的值及其他需要传输编码字段的编码方式(如标题中的多国语言)。MIME_MediaTypes类设置邮件各部分内容的类型,决定邮件中Content-Type字段的值。而Encoding类不用说,决定了charset的值。关于这些设置的具体作用下文还将提到,这里略过。
4、测试
下表是通过网络搜集的各大SMTP服务器的配置情况,可以选择使用这些配置进行测试:
服务商 | SMTP地址 | SMTP端口 | EnableSsl |
gmail | smtp.google.com | 25, 465 or 587 | true |
126 | smtp.126.com | 25 | false |
163 | smtp.126.com | 25 | false |
hotmail | smtp.live.com | 25 | true |
sina | smtp.sina.com | 25 | false |
sohu | smtp.sohu.com | 25 | false |
新建控制台应用程序,测试发送只包含正文的简单邮件:
class Program
{
static void Main(string[] args)
{ var h1 = new ConfigHost()
{
Server = "smtp.gmail.com",
Port = ,
Username = "******@gmail.com",
Password = "******",
EnableSsl = true
};
var m1 = new ConfigMail()
{
Subject = "Test",
Body = "Just a test.",
From = "******@gmail.com",
To = new string[] { "******@gmail.com" },
}; var agents = new List<ISendMail>() { new UseNetMail(), new UseOpenSmtp(), new UseLumiSoft() };
foreach (var agent in agents)
{
var output = "Send m1 via h1 " + agent.GetType().Name + " ";
Console.WriteLine(output + "start");
try
{
agent.CreateHost(h1);
m1.Subject = output;
agent.CreateMail(m1);
agent.SendMail();
Console.WriteLine(output + "success");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine(output + "end");
Console.WriteLine("-----------------------------------");
}
Console.Read();
}
}
通过gmail发送邮件时,OpenSmtp由于不支持SSL发送失败,NetMail使用587端口能够成功发送,LumiSoft使用465端口能够成功发送。查阅Gmail相关文档,描述说Gmail的465端口使用SSL协议,而587端口使用TLS协议,但587是需要STARTTLS命令支持才能提升为TLS。在命令提示符下测试发现的确需要在发送STARTTLS命令后才能使用TLS协议:
> telnet smtp.gmail.com
mx.google.com ESMTP o5sm40420786eeg. - gsmtp
EHLO g1
-mx.google.com at your service, [173.231.8.212]
-SIZE
-8BITMIME
-STARTTLS
-ENHANCEDSTATUSCODES
CHUNKING
AUTH LOGIN
5.7. Must issue a STARTTLS command first. o5sm40420786eeg. – gsmtpSTARTTLS220
STARTTLS
2.0. Ready to start TLS
…
QUIT
对于TLS与STARTTLS人们经常搞混,这里找到一篇关于它们的解释,请点击这里。
因而LumiSoft如果连接gmail服务器时还需明确发送STARTTLS命令,已经发现LumiSoft有相关方法SMTP_Client.StartTLS(),连接gmail相较其他smtp服务器还是较为复杂些。另外一些服务器要求邮件配置中的Username必须与From相一致,需要特别注意。
测试发送带附件和内嵌资源的邮件:
class Program
{
static void Main(string[] args)
{
var h2 = new ConfigHost()
{
Server = "smtp.163.com",
Port = ,
Username = "******@163.com",
Password = "******",
EnableSsl = false
};
var m2 = new ConfigMail()
{
Subject = "Test",
Body = "Just a test. <br/><img src='cid:" + Convert.ToBase64String(Encoding.Default.GetBytes("Resource.jpg")) + "' alt=''/> ",
From = "******@163.com",
To = new string[] { "******@163.com" },
Attachments = new string[] { @"E:\Test\SendMail\Attachment.pdf" },
Resources = new string[] { @"E:\Test\SendMail\Resource.jpg" }
};
var agents = new List<ISendMail>() { new UseNetMail(), new UseOpenSmtp(), new UseLumiSoft() };
foreach (var agent in agents)
{
var output = "Send m2 via h2 " + agent.GetType().Name + " ";
Console.WriteLine(output + "start");
try
{
agent.CreateHost(h2);
m2.Subject = output;
agent.CreateMultiMail(m2);
agent.SendMail();
Console.WriteLine(output + "success");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine(output + "end");
Console.WriteLine("-----------------------------------");
}
Console.Read();
}
}
C#邮件发送问题(一)的更多相关文章
- .NET开发邮件发送功能的全面教程(含邮件组件源码)
今天,给大家分享的是如何在.NET平台中开发“邮件发送”功能.在网上搜的到的各种资料一般都介绍的比较简单,那今天我想比较细的整理介绍下: 1) 邮件基础理论知识 2) ...
- J2EE 邮件发送那些事儿
距离自己写的关于java邮件发送的第一篇博客已经有很长一段时间了,现在回过头看看.虽然代码质量方面有待提高,整体结构也不怎样,但是基本思路和过程还是比较纯的.现在有空写写J2EE中邮件发送的开发,实际 ...
- 结合ABP源码实现邮件发送功能
1. 前言 2. 实现过程 1. 代码图(重) 2.具体实现 2.1 定义AppSettingNames及AppSettingProvider 2.2 EmailSenderConfiguration ...
- SSH项目里面 忘记密码的邮件发送功能
package com.xxx.util; import java.util.Date; import java.util.Properties; import javax.mail.Address; ...
- [UWP]UWP中获取联系人/邮件发送/SMS消息发送操作
这篇博客将介绍如何在UWP程序中获取联系人/邮件发送/SMS发送的基础操作. 1. 获取联系人 UWP中联系人获取需要引入Windows.ApplicationModel.Contacts名称空间. ...
- java spring 邮件发送
开发中经常会遇到发送邮件进行用户验证,或者其它推送信息的情况,本文基于spring,完成邮件的发送,主要支持普通文本邮件的发送,html文本邮件的发送,带附件的邮件发送,没有实现群发.多个附件发送等需 ...
- Java邮件发送与接收原理
一. 邮件开发涉及到的一些基本概念 1.1.邮件服务器和电子邮箱 要在Internet上提供电子邮件功能,必须有专门的电子邮件服务器.例如现在Internet很多提供邮件服务的厂商:sina.sohu ...
- c#实现邮件发送链接激活
2016-08-24 10:09:52 public void MailSend(string email) { MailMessage MyMail = new MailMessage(); MyM ...
- .Net(C#)最简单的邮件发送案例
一.序言 刚开始接触邮件发送功能的时候,在网上找的资料都挺复杂的!对于新手入门有点难(至少对于本人来说,第一次接触的时候确实不容易).这里就写一段简单的邮箱发送代码,备忘,也给新手一个参考(相关类的字 ...
- SpringMVC 邮件发送
<!--邮件发送实现类--> <bean id="javaMailSender" class="org.springframework.mail.jav ...
随机推荐
- Scalaz(13)- Monad:Writer - some kind of logger
通过前面的几篇讨论我们了解到F[T]就是FP中运算的表达形式(representation of computation).在这里F[]不仅仅是一种高阶类型,它还代表了一种运算协议(computati ...
- 内存溢出与jvm参数配置 ***最爱那水货
第一类内存溢出,也是大家认为最多,第一反应认为是的内存溢出,就是堆栈溢出: 那什么样的情况就是堆栈溢出呢?当你看到下面的关键字的时候它就是堆栈溢出了: Java.lang.OutOfMemoryErr ...
- Oracle的AWR报告分析
* 定义:awr报告是oracle 10g下提供的一种性能收集和分析工具,它能提供一个时间段内整个系统资源使用情况的报告,通过这个报告,我们就可以了解一个系统的整个运行情况,这就像一个人全面的体检报告 ...
- Java空白final
Java 1.1允许我们创建"空白final",它们属于一些特殊的字段.尽管被申明为final,但却未得到一个初始值. 无论在哪种情况下,空白final都必须在实际使用前得到正确的 ...
- Properties+重温Map+本地计数器
昨天想写一个记账本,发现并不能把项目名称与内容关联起来,于是乎我想到了map,可是又不知道map储存到文件中又怎么读出来,幸好今天遇到了properties Properties是Hashtable的 ...
- 为友盟消息推送开发的PHP SDK(composer版):可以按省发Android push
一直以来APP希望按省市县推送Android push,只能自己分析用户经纬度,打tag发送. 现在终于有服务商提供了. 友盟消息推送 可以“按省推送”,很方便. 我为友盟做了PHP SDK(comp ...
- CSS3动画处理浏览器内核时候前缀(兼容性)
Gecko内核 css前缀为"-moz-" 火狐浏览器 WebKit内核 css前缀为"-webkit-" Comodo Drangon(科摩多龙), ...
- JS中数组去除重复的方法
function unique(arr) { var result = [], hash = []; for (var i = 0, elem; (elem = arr[i]) != null; i+ ...
- JavaScript 左右上下自动晃动,自动移动。
最近做了一个项目,本来用css3动画做的,不兼容ie,用js做了个,分享给大家. 代码修改了下,上下左右四个模块,顺时针转动. <!DOCTYPE html> <html> & ...
- python任务执行之线程,进程,与协程
一.线程 线程为程序中执行任务的最小单元,由Threading模块提供了相关操作,线程适合于IO操作密集的情况下使用 #!/usr/bin/env python # -*- coding:utf-8 ...